目录
- 最简单的hash表
- 子串变位词
- 排序
- 最简单的桶排序
- 字典序
- 快排
- 练习1 :0-1交换
- 练习2:交换星号
- 最简单的去重
- 最简单的队列
- template模版
- 双端队列
- vector or list
- vector
- 队列结构存在于两种算法
- 广度优先搜索
- 贝尔曼福特算法(Bellman-Ford)
- 最简单的优化
- 输入or输出的艺术
- %d,%05d,%-5d,%.5d的区分
- gets/puts/fgets
- cin、cin.get()、cin.getline()、getline()
- ascii码
- 打印形状
- 打印沙漏
- 二项式系数&杨辉三角
- 各种数字运算
- 勾股定理
- 膜运算
- 循环移位
- 6位小数double “%6lf”
- 单位分数之和
- 埃及分数(贪心法)
- 进阶
- dfs or 回溯法
- 练习1 数独游戏
- 练习2:排列数字
- 回文串——用栈解密
最简单的hash表
子串变位词
变位词:组成字母完全相同但顺序不一样,如abc acb。给定两个串a和b,问b是否是a的子串的变位词。例如输入a = hello, b = lel, lle, ello都是true,但是b = elo是false。
1.方法1:暴力
依次遍历b中字母检查是否在a里
2.方法2:先给两个串排序,然后比较
3.制作hash表
//制作a串的hash表
for (int i = 0; i < lena; ++i)
num[a[i] – ‘a’]++;利用ascii码的差作为下标,形成对应的映射 如果出现0的情况则不是变位词
for (int j = 0; i < lenb; ++i)
if (num[b[i] – ‘a’] == 0)
{ cout<<"false"<<endl;
return 0;}
cout<<"true"<<endl;
排序
最简单的桶排序
利用数组序号的自顺序进行排序输入输出
int book[1001],i,j,t,n;//size=num-1
for(i=0;i<=1000;i++)
book[i]=0;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&t);
book[t]++;
}
for(i=1000;i>=0;i--)
for(j=1;j<book[i];j++)//book[i]里有几就打印几次
printf("%d",i);
字典序
先按照第一个字母、以 a、b、c……z 的顺序排列;如果第一个字母一样,那么比较第二个、第三个乃至后面的字母。如果比到最后两个单词不一样长(比如,sigh 和 sight),那么把短者排在前。
快排
基于二分法的思想。
练习1 :0-1交换
把一个0-1串(只包含0和1的串)进行排序,你可以交换任意两个位置,问最少交换的次数?
分析: 快排partition:最左边的那些0和最右边的那些1都可以不管;从左开始扫到第一个出现1的位置,从右扫第一个出现0的位置 然后进行交换即可。
int answer = 0; //次数
for (int i = 0, j = len – 1; i < j; ++i, --j) {
for (;(i < j) && (a[i] == ‘0’);++i);
for (;(j > i) && (a[j] == ‘1’); --j);
if (i <j) ++answer;
}
练习2:交换星号
一个字符串只包含和数字,请把它的星号都放开头。
//这个方法会打乱数字顺序
for (int i = 0, j = 0; j < n; ++j)
if (s[j] == ‘*’) swap(s[i++], s[j]);
//不打乱数字顺序的方法 直接倒着覆盖 省空间
int j = n - 1;
for (int i = n - 1; i >= 0; --i)
if (isdigit(s[i])) s[j--] = s[i];//isdigit是否是数字
for (; j >= 0; --j) s[j] = ‘*’;
最简单的去重
//假设经过排序之后
for(i=2;i<=n;i++)
if(a[i]!=a[i-1])
printf("%d",a[i]);
最简单的队列
利用数组,再加上设置两个指针head,tail构成其基本元素。
出队:head++;
入队:q[tail]=x;tail++;
//封装好的简易queue
struct queue
{
int data[100];
int head;
int tail;
}
struct queue q;
void init_q(queue &q){
q.head=1;
q.tail=1;
}
int isfull_q(queue &q){
if(q.tail>=99)
return 1;
else return 0;
}
int isempty_q(queue &q){
if(q.head<=1)
return 1;
else return 0;
}
int insert_q(queue &q,int num)
{
if(isfull_q(q)!=1)
{ q.data[q.tail]=num;
q.tail++;
}
}
int qout_q(queue &q,int &n)
{
if(isempty_q(q)!=1)
{
n=q.data[q.head];
q.head++;
}
}
/*c++ stl库中的queue实现
C++ STL中给出的stack和queue类的声明为:
由stack和queue的声明不难发现,它们本质上并不是容器,而是容器适配器,stack和queue的底层都是调用名为deque(双端队列)的容器的接口来实现的。
所谓适配器,通俗讲就是一种设计模式,即:一套被反复使用的、为多数人所知晓的、经过分类编目的代码设计经验的总结,设计模式可以将一个类接口转换为用户希望的其它类接口。
template <class T, class Container = deque<T>> class stack;
template <class T, class Container = deque<T>> class queue;
template模版
在没有泛型编程时,只能使用函数重载逐个写,每次都要摘过来一段代码。
重载的函数仅仅是参数列表中的类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数代码的可维护性比较低,一个出错可能所有的重载均出错。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础模板,分为函数模板和类模板。函数模板只是一个蓝图,在实例化时,根据推导的参数的类型来生成特定的版本。
template<typename T1, typename T2…, typename Tn>typename是用来定义模板参数的关键字,也可以使用class(切记:不能使用struct代替class)
双端队列
deque是双端队列容器,它可以同时支持O(1)时间复杂度下的头部的插入删除和在尾部的插入和删除,但无法在O(1)时间复杂度的下实现在中间某个位置插入和删除数据。可以通过下标来访问任意位置处的元素。deque底层空间连续,不需要频繁地申请和释放小块的内存空间,申请和释放空间的消耗相对于list较低,不需要存储每个数据时都存储两个指针来指向前一个和后一个数据的位置。deque扩容时不需要复制数据,而且deque一次开辟一小块空间,空间利用率高。不适合遍历和随机访问,因为deuqe在进行遍历时,迭代器需要频繁地去检查是否移动到了某一小段空间的边界位置,效率低下。
对于vector,它可以支持O(1)时间复杂度下的尾插数据和尾删数据,但要实现头插和头删则需要O(N)的时间复杂度,vector头插和头删数据操作需要移动后面的所有数据,vector在扩容时需用复制已有数据到新的内存空间,并且每次扩容得到新空间的容量一般是原来容量的1.5倍或2倍,vector扩容时复制数据需要一定的消耗且空间浪费相对于deque较多。
对于list,它可以实现在O(1)时间复杂度下任意位置的插入和删除数据,但不能支持通过下标来进行随机访问。
所以,在某种程度上,我们可以认为deque兼具了vector和list的一些优点。
如果没有deuqe容器,那么,stack只能使用vector或list来实现,queue只能使用list来实现。
相对于vector实现stack:deque的扩容代价低,扩容不用拷贝数据,空间浪费较少。
相对于list实现stack:deque不用频繁申请和释放小块内存空间,CPU高速缓存命中率高,申请和释放内存空间的次数少。
相对于list实现queue:deque不用频繁申请和释放小块内存空间,CPU高速缓存命中率高,申请和释放内存空间的次数少。
————————————————
版权声明:本文为CSDN博主「【Shine】光芒」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43908419/article/details/129871199
vector or list
一、vector结构上的优点:
由于底层结构是一块连续的空间,所以可以支持下标’‘[]’'随机访问的形式。
cpu高速缓存命中率高。
尾插尾删除效率高,相比list
二、vector结构上的缺点:
越远离头部位置(除了尾部之外)的插入、删除效率低。
扩容有消耗,存在一定的空间的浪费,无论是手动扩还是自动扩,一般来说扩二倍比较合适。
三、list结构上的优点:
任何位置的插入删除的效率比较高,相比list。
按需申请释放节点,空间利用率高,不存在扩容操作。
四、list结构上的缺点:
不支持下标随机访问。
cpu高速缓存命中率低。
五、使用场景:
若存在大量的中间插入删除,考虑用list;否则用vector。list与vector互补配合。
vector
vector容器的功能和数组非常相似,使用时可以把它看成一个数组
vector和普通数组的区别:
1.数组是静态的,长度不可改变,而vector可以动态扩展,增加长度
2.数组内数据通常存储在栈上,而vector中数据存储在堆上
动态扩展:(这个概念很重要)
动态扩展并不是在原空间之后续接新空间,而是找到比原来更大的内存空间,将原数据拷贝到新空间,释放原空间
注意:使用vector之前必须包含头文件 #include < vector>
1.构造
void TestVector()
{
vector<int> v1; //无参构造
vector<int> v2(3, 0); //用3个0去初始化
vector<int> v3(v1); //拷贝构造
vector<int> v4(v1.begin(), v1.end()); //迭代器区间构造
}
2.判空、查询/修改大小,容量,清除
void text03()
{
vector<int> v1;
cout << v1.empty() << endl;
cout << v1.size() << endl; //17
cout << v1.capacity() << endl;//不同编译编译器的扩容机制不一样,因此空间的大小不一定
v1.clear(); //调用clear
//重新指定容器大小使其变长
v1.resize(10); //调用4,增加的长度默认值为0(不指定情况下默认为0)
v1.resize(15, 9); //调用5,增加的长度赋值为9
//重新指定容器大小使其变短
v1.resize(10); //调用4,删除了上一步中最后赋值为9的5个长度
v1.resize(5); //调用5,删除了上一步中默认值为0的5个长度
}
3.赋值、插入、删除
void text02()
{
vector<int> v1,v2;
for (int i = 0; i < 5; ++i)
{
v1.push_back(i);
}
v2 = v1; //调用1,赋值运算符重载
vector<int> v3,v4;
v3.assign(v1.begin(), v1.end());//调用2,区间赋值
v4.assign(5, 9); //调用3,放入5个9
}
void text04()
{
vector<int> v1;
v1.push_back(6);//调用1,尾部插入元素6
v1.pop_back();//调用2,删除最后一个元素
v1.insert(v1.begin(),20);//调用3,在首位插入20
v1.insert(v1.end(), 3, 20);//调用4,在尾部插入3个20
v1.erase(v1.begin()); //调用5,在首位删除一个元素
v1.erase(v1.begin(),v1.end()); //调用6,删除首位到末尾所有元素,也就是删除全部元素
v1.clear();//调用7,清空所有元素
v1.swap(v2); //调用互换函数,容器v1,v2互换
}
4.查询数据
void text05()
{
for (int i = 0; i < v.size(); ++i)
{
cout << v.at(i) << " ";//调用1
}
//利用[]访问v
for (int i = 0; i < v.size(); ++i)
{
cout << v[i] << " ";//调用2
}
cout << "容器中第一个元素是:" << v.front() << endl;//调用3
cout << "容器中最后一个元素是:" << v.back() << endl;//调用4
}
5.迭代器操作
void TestVector()
{
vector<int> v1(10, 1);
vector<int>::iterator vt = v1.begin();//rbegin,rend反向起止迭代器
while (vt != v1.end())
{
cout << *vt << " ";
++vt;
}
}
队列结构存在于两种算法
广度优先搜索
贝尔曼福特算法(Bellman-Ford)
典型最短路径算法,用于计算一个节点到其他节点的最短路径。(Dijkstra算法也是)
基本原理:逐遍的对图中每一个边去迭代计算起始点到其余各点的最短路径,执行N-1遍,最终得到起始点到其余各点的最短路径。(N为连通图结点数)
名词解释:
- 松弛操作:不断更新最短路径和前驱结点的操作。
- 负权回路:绕一圈绕回来发现到自己的距离从0变成了负数,到各结点的距离无限制的降低,停不下来。
与迪杰斯特拉算法的区别:
1. 迪杰斯特拉算法是借助贪心思想,每次选取一个未处理的最近的结点,去对与他相连接的边进行松弛操作;贝尔曼福特算法是直接对所有边进行N-1遍松弛操作。
2. 迪杰斯特拉算法要求边的权值不能是负数;贝尔曼福特算法边的权值可以为负数,并可检测负权回路。
————————————————
版权声明:本文为CSDN博主「lzh1366」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_40347399/article/details/119879750
//python代码乱入 转自上述博主
#定义邻接矩阵 记录各城市之间的距离
weight = [[INF if j!=i else 0 for j in range(N)] for i in range(N)]
#判断负权回路
for i in range(N):
for j in range(N):
if dist[j] > dist[i] + weight[j][i]:
raise ValueError("存在负权回路")
#松弛n-1次,因为最短路径的深度最多是n-1,n为结点数目
for i in range(N-1):
change = False
#分别遍历边的两个顶点,从而实现遍历所有边。
for j in range(N):
for k in range(N):
if dist[j] > dist[k] + weight[j][k]:
dist[j] = dist[k] + weight[j][k]
#记录更新该结点的结点编号
last_update[j] = k
#标记更改状态
change = True
#如果本轮未作出更改,说明已完成
if not change:
break
最简单的优化
如果未发生更改,则跳出循环。这个最简单的优化运用广泛,见冒泡排序/Bellman-Ford算法等。
输入or输出的艺术
%d,%05d,%-5d,%.5d的区分
%d是普通的输出
%5d是将数字按宽度为5,采用右对齐方式输出,若数据位数不到5位,则左边补空格
%-5d就是左对齐
%05d,和%5d差不多,只不过左边补0
%.5d从执行效果来看,和%05d一样
gets/puts/fgets
gets用来输入一行字符串(注意:gets识别换行符\‘0’作为输入结束,因此 scanf完一个整数后,如果要使用gets,需要先用 getchar接收整数后的换行符),并将其存放于一维数组(或二维数组的一维)中;或者删去末尾的’0’
puts用来输出一行字符串,即将一维数组(或二维数组的一维)在界面上输出,并紧跟一个换行。
虽然用 gets() 时有空格也可以直接输入,但是 gets() 有一个非常大的缺陷,即它不检查预留存储区是否能够容纳实际输入的数据,换句话说,如果输入的字符数目大于数组的长度,gets 无法检测到这个问题,就会发生内存越界,所以编程时建议使用 fgets()。
fgets() 虽然比 gets() 安全,但安全是要付出代价的,代价就是它的使用比 gets() 要麻烦一点,有三个参数。它的功能是从 stream 流中读取 size 个字符存储到字符指针变量 s 所指向的内存空间。它的返回值是一个指针,指向字符串中第一个字符的地址。# include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
其中:s 代表要保存到的内存空间的首地址,可以是字符数组名,也可以是指向字符数组的字符指针变量名。size 代表的是读取字符串的长度。stream 表示从何种流中读取,可以是标准输入流 stdin,也可以是文件流,即从某个文件中读取,这个在后面讲文件的时候再详细介绍。标准输入流就是前面讲的输入缓冲区。所以如果是从键盘读取数据的话就是从输入缓冲区中读取数据,即从标准输入流 stdin 中读取数据,所以第三个参数为 stdin。
不用数字符长度!fget() 函数中的 size 如果小于字符串的长度,那么字符串将会被截取;如果 size 大于字符串的长度则多余的部分系统会自动用 ‘\0’ 填充。所以假如你定义的字符数组长度为 n,那么 fgets() 中的 size 就指定为 n–1,留一个给 ‘\0’ 就行了。
cin、cin.get()、cin.getline()、getline()
根据cin>>sth 中sth的变量类型读取数据,这里变量类型可以为int,float,char,char*,string等诸多类型。这一输入操作,在遇到结束符(Space、Tab、Enter)就结束,且对于结束符,并不保存到变量中。注意:最后一个enter也在缓冲区。
cin.get(字符数组名,接收长度,结束符)
其中结束符意味着遇到该符号结束字符串读取,默认为enter,读取的字符个数最多为(长度 - 1),因为最后一个为’\0’。要注意的是,cin.get(字符数组名,接收长度,结束符)操作遇到结束符停止读取,但并不会将结束符从缓冲区丢弃。
输入字符后,其结束符(如默认的Enter)会保留在缓冲区中,当下次读入时,又会再读入,此时就可以用到cin.get()读掉输入缓冲区不需要的字符
cin.getline(字符数组名,接收长度,结束符)
其用法与cin.get(字符数组名,接收长度,结束符)极为类似。cin.get()当输入的字符串超长时,不会引起cin函数的错误,后面若有cin操作,会继续执行,只是直接从缓冲区中取数据。但是cin.getline()当输入超长时,会引起cin函数的错误,后面的cin操作将不再执行。
cin.get()每次读取一整行并把由Enter键生成的换行符留在输入队列中,需要用cin.get(ch);读掉它。然而cin.getline()每次读取一整行并把由Enter键生成的换行符抛弃。
getline(istream is,string str,结束符)
同样,此处结束符为可选参数(默认依然为enter)。然而,getline()与前面的诸多存在的差别在于,它string库函数下,而非前面的istream流,所有调用前要在前面加入#include< string>。与之对应这一方法读入时第二个参数为string类型,而不再是char*,要注意区别。另外,该方法也不是遇到空白字符(tab, space, enter(当结束符不是默认enter时))就结束输入的,且会丢弃最后一个换行符。
ascii码
练习:输入一个字符串,求它包含多少个单词。单词间以一个或者多个空格分开。第一个单词前,最后一个单词后也可能有0到多个空格。比如:" abc xyz" 包含两个单词,"ab c xyz " 包含3个单词。
int get_word_num(char* buf)
{
int n = 0;
int tag = 1;
char* p = buf;
// *p==0空 *p==13回车 *p==10换行
for(;*p!=0 && *p!=13 && *p!=10;p++){
if(*p==' ' && tag==0) tag=1;
if( *p!=' ' && tag==1 ) { n++; tag=0; }
//当前一位是空格,但后一位不是空格时就代表一个单词出现,n++。
}
return n;
}
int main()
{
char buf[1000];
fgets(buf,1000,stdin);
printf("%d\n", get_word_num(buf));
return 0;
}
打印形状
打印沙漏
***** 第一层 空格数0
*** 第二次 空格数1
* 第三层 空格数2
***
*****
给定任意N个符号,不一定能正好组成一个沙漏。要求打印出的沙漏能用掉尽可能多的符号。
1.首先确定最多用多少个符号?
2n^2-1 n逐渐递增,大于N时跳出,注意保存n,它是三角形的层数。
2.打印倒三角 倒三角层数是倒着排的,第三层,第二层,第一层。第n层符号数有2n-1个。当层数小于n层,假设为a层时需要先输出n-a个空格,再输出符号;行末尾的空格不用管,直接回车。
3.打印正三角 同理
二项式系数&杨辉三角
二项式的系数规律其排列规律:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
要求建立N行的杨辉三角形。
#define N 8
int main()
{
int a[N][N];
int i,j;
for(i=0; i<N; i++){
a[i][0] = 1;
a[i][i] = 1;
}
for(i=1; i<N; i++){
for(j=1; j<i; j++) a[i][j]=a[i-1][j-1]+a[i-1][j];
}
for(i=0; i<N; i++){
for(j=0; j<=i; j++) printf("%-5d", a[i][j]);
printf("\n");
}
return 0;
}
各种数字运算
int的取值范围为: -231——231-1,即-2147483648——2147483647 是一个十位数
勾股定理
已知直角三角形的斜边是某个整数,并且要求另外两条边也必须是整数。
求满足这个条件的不同直角三角形的个数。
【数据格式】
输入一个整数 n (0<n<10000000) 表示直角三角形斜边的长度。
要求输出一个整数,表示满足条件的直角三角形个数。
例如,输入:
5
程序应该输出:
1
再例如,输入:
100
程序应该输出:
2
再例如,输入:
3
程序应该输出:
0
要注意的是平方之后容易超出int的范围,用上longlong,最好不要用double,double在判等的时候精度问题容易错。暴力的时候穷举到 c/sqrt(2)就行了,也是一个优化点。
#include<stdio.h>
#include<math.h>
int main()
{
long long aa,bb,cc;
int a,b,c,count=0;
scanf("%d",&c);
cc = c*c;
for(a=1;a<c/sqrt(2);a++)
{
aa = a*a;
b = sqrt(cc-aa);//b等于斜边的平方-另一边的平方再开方
if(b*b+aa==cc)
{
count++;//加1
}
}
printf("%d\n",count);
return 0;
}
有如下的加法算式。其中每个汉字代表一个数字,注意对齐。求“让我怎能过大年 ”所代表的整数。所有数字连在一起,中间不要空格。
年
大年
过大年
能过大年
怎能过大年
我怎能过大年
+ 让我怎能过大年
------------------
能能能能能能能
暴力? 套7层循环,复杂度太高。
膜运算
#include<stdio.h>
int main()
{
int sum;
int i;
int temp;
int a,b,c,d,e,f,g;//代表每一位上的数字
for(i=9992299;i>=1000000;i--)
{
a = i%10;//个位
b = i/10%10;//十位
c = i/100%10;//百位
d = i/1000%10; //千位
e = i/10000%10;//万位
f = i/100000%10;//十万位
g = i/1000000%10;//百万位
//printf("%d\t%d\t%d\t%d\t%d\t%d\t%d\n",a,b,c,d,e,f,g);
//break;
temp=d*1000000+d*100000+d*10000+d*1000+d*100+d*10+d*1;
//printf("%d\n",temp);
//break;
a=0+a*1;
b=a+b*10;
c=b+c*100;
d=c+d*1000;
e=d+e*10000;
f=e+f*100000;
g=f+g*1000000; //也可以直接g=i;
//printf("%d\t%d\t%d\t%d\t%d\t%d\t%d\n",a,b,c,d,e,f,g);
//break;
sum=a+b+c+d+e+f+g;
if(sum==temp)
{
printf("%d\n",i);
break;
}
}
return 0;
}
循环移位
int fun(int n)//一次循环移位
{
int a = n%10;//取出最后一位 比如12345得出a=5,同理%100取出倒数第二位
int b = n/10;//取出前面4位 b=1234
return a*10000+b; //经典的循环返回, 5*10000+1234=51234
}
//封装好的多次循环移位
void roll(int n,int cnt)//n要循环移位的数,cnt循环移动几次
{
int n_=n;
int a,b,sum;
int i=0;
while(i<cnt)
{
a = n_%10;//取出最后一位 比如12345得出a=5,同理%100取出倒数第二位
b = n_/10;//取出前面4位 b=1234
sum=a*10000+b;
std::cout<<sum<<std::endl;
n_=sum;
i++;
}
}
练习题 1193是个素数,对它循环移位后发现:1931,9311,3119也都是素数,这样特征的数叫:循环素数。你能找出具有这样特征的5位数的循环素数吗?当然,这样的数字可能有很多,请写出其中最大的一个。注意:答案是个5位数,不要填写任何多余的内容。
我的思路:99999 ~ 11111的n的降序循环 里面套一层五次循环移位的循环 顺便判断一个函数即判断是否是素数( 模2 ~ 根号n的循环 如果都模不等于0则返回它 )然后程序结束
6位小数double “%6lf”
如果x的x次幂结果为10(参见【图1.png】),你能计算出x的近似值吗?显然,这个值是介于2和3之间的一个数字。请把x的值计算到小数后6位(四舍五入),并填写这个小数值。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<stdlib.h>
#include<algorithm>
#include<cmath>
using namespace std;
int main()
{
double a=2.0;
for(;a<3;a+=0.0000001)//要比要求的6位小数多出一位才能四舍五入
{
if(fabs(pow(a,a)-10.0)<0.000001)
break;
}
printf("%6lf",a); //lf是double格式
return 0;
}
单位分数之和
埃及分数(贪心法)
埃及分数:分子是1的分数,也叫单位分数。古代埃及人在进行分数运算时,只使用分子是1的分数。如:8/11=1/2+1/5+1/55+1/110。
真分数:分子比分母小的分数,叫做真分数。真分数的分数值小于1。如1/2,3/5,8/9等。输入一个真分数,请将该分数分解为埃及分数。一个真分数的埃及分数表示并不唯一。
贪心法,则是要让一个真分数被埃及分数表示的个数最少,每次选择不大于真分数的最大埃及分数。
假设需要求解真分数 A / B (A 与 B 不可约),
那么假设 B = A * C + D, B / A = C + D / A < C + 1,A / B > 1 / (C + 1);
按照贪心的思想,1 / (C + 1) 为 A / B 分解中最大的那个分子为 1 的真分数。
假设 E = (C + 1),那么相减以后得到 A / B - 1 / E = (A * E - B ) / B * E,
那么得到新的A = A * E - B,B = B * E,然后对新的 A / B 进行约分,保证下一轮的 A 与 B 不可约。
如此循环,当 A = 1 是表明结束循环,得到答案。
#include<bits/stdc++.h>
using namespace std;
void EgyptFraction(int A,int B){
cout << A << '/' << B << '=';
int E,R;
while(A != 1){
E = B / A + 1; //B / A = C.
cout << "1/" << E << '+';
A = A * E - B;
B = B * E;
R = __gcd(A,B);
if(R > 1){
A /= R;
B /= R;
}
}
cout << "1/" << B;//A 是 1 了直接输出 1 / B 即可,此时结束分解。
}
int main(){
int A,B;
cin >> A >> B;
EgyptFraction(A,B);
return 0;
}
进阶
形如:1/a 的分数称为单位分数。
可以把1分解为若干个互不相同的单位分数之和。
例如:
1 = 1/2 + 1/3 + 1/9 + 1/18
1 = 1/2 + 1/3 + 1/10 + 1/15
1 = 1/3 + 1/5 + 1/7 + 1/9 + 1/11 + 1/15 + 1/35 + 1/45 + 1/231
等等,类似这样的分解无穷无尽。
我们增加一个约束条件:最大的分母必须不超过30
请你求出分解为n项时的所有不同分解法。
数据格式要求:
输入一个整数n,表示要分解为n项(n<12)
输出分解后的单位分数项,中间用一个空格分开。
每种分解法占用一行,行间的顺序按照分母从小到大排序。
例如,
输入:
4
程序应该输出:
1/2 1/3 1/8 1/24
1/2 1/3 1/9 1/18
1/2 1/3 1/10 1/15
1/2 1/4 1/5 1/20
1/2 1/4 1/6 1/12
再例如,
输入:
5
程序应该输出:
1/2 1/3 1/12 1/21 1/28
1/2 1/4 1/6 1/21 1/28
1/2 1/4 1/7 1/14 1/28
1/2 1/4 1/8 1/12 1/24
1/2 1/4 1/9 1/12 1/18
1/2 1/4 1/10 1/12 1/15
1/2 1/5 1/6 1/12 1/20
1/3 1/4 1/5 1/6 1/20
资源约定:
峰值内存消耗 < 256M
CPU消耗 < 2000ms
先研究一下数学原理
1=30/30
这个分母就是30,分子也是30,将分子的30做分解就可以了。列出1-29之内相加等于30的各种可能,然后将这些数和分母约分就可以了
比如:30=2+3+25
则1=(2+3+25)/30
1=1/15+1/10+5/6
加了限制项n 时,就更加好办了,就是求n个数相加等于30
等式可以认为是(a1+a2+...am)/K,很好理解,a1,...am必然是K的所有约数的和的组合。因此最后题目就是转化为求一个数K的所有约数,题目K最大才30,之后求出所有约数中,n个数的和等于K的组合。
比如:K=30,30=2*3*5,根据“约数个数定理”,约数有8个,除去自己本身和1就剩6个,为2,3,5,6,10,15,
如果你输入n=4,那么就是求这6个约数中,哪4个相加正好等于30的所有组合,根据组合原理,C(7,4)才210种组合,因此循环不会很久。
题目没规定K值,因此,需要K从1到30循环,对每一次循环的K,找出所有所有约数,并对所有约数个数不小于n的情况,循环找出所有n个约数等于K的组合。
拆分步骤:
1、约数个数定理
2、求一个数的所有约数
3、从N个数中任选M个数相加的和等于K(循环应该就能解决)
第二种方法:dfs
dfs需要具备的基本元素:1.初值 2.什么时候跳出 3.迭代方式(搜索空间要包括全部可探索空间)
#include <iostream>
#include <cmath>
using namespace std;
int n,ans[15];
const double eps=1e-9;
void dfs(double sum,int cnt,double now)//sum 分数加和,cnt当前几个分数相加,now最大分母的值
{
if(cnt==n){
if(abs(sum-1)<eps){
for(int i=0;i<n;i++)
cout<<1<<'/'<<ans[i]<<' ';//ans数组存储依次相加分数的分母
cout<<endl;
}
return ;
}
if(sum>=1||cnt>n||now>30)
return ;
dfs(sum,cnt,now+1);
ans[cnt]=now;
dfs(sum+1/now,cnt+1,now+1);
return ;
}
int main()
{
cin>>n;
dfs(0,0,1);
return 0;
}
dfs or 回溯法
DFS 英文名,Depth First Search,中文名 深度优先搜索,是图的一种搜索算法,每一个可能的分支路径深入到不能再深入为止,且每个节点只能访问一次。
深度优先搜索算法跟图结构紧密相关,任何涉及深度度优先搜索的问题,都伴随着图。深度度优先搜索的能够在图结构里搜索到通往特定终点的一条或者多条特定路径。
回溯算法是系统地搜索问题的解的方法。某个问题的所有可能解的称为问题的解空间,若解空间是有限的,则可将解空间映射成树结构。任何解空间可以映射成树结构的问题,都可以使用回溯法。回溯法是能够在树结构里搜索到通往特定终点的一条或者多条特定路径。
回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试,从而搜索到抵达特定终点的一条或者多条特定路径。
值得注意,回溯法以深度优先搜索的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。
不能简单的说:回溯算法 = 深度优先搜索 + 剪枝函数。因为并不是所有图都是树。深度优先搜索适用于所有图。而回溯算法只适用于树结构。
练习1 数独游戏
玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个同色九宫内的数字均含1-9,不重复。
数独的答案都是唯一的,所以,多个解也称为无解。
本图的数字据说是芬兰数学家花了3个月的时间设计出来的较难的题目。但对会使用计算机编程的你来说,恐怕易如反掌了。
本题的要求就是输入数独题目,程序输出数独的唯一解。我们保证所有已知数据的格式都是合法的,并且题目有唯一的解。
格式要求,输入9行,每行9个字符,0代表未知,其它数字为已知。
输出9行,每行9个数字表示数独的解。
例如:
输入(即图中题目):
005300000
800000020
070010500
400005300
010070006
003200080
060500009
004000030
000009700
程序应该输出:
145327698
839654127
672918543
496185372
218473956
753296481
367542819
984761235
521839764
再例如,输入:
800000000
003600000
070090200
050007000
000045700
000100030
001000068
008500010
090000400
程序应该输出:
812753649
943682175
675491283
154237896
369845721
287169534
521974368
438526917
796318452
资源约定:
峰值内存消耗 < 256M
CPU消耗 < 2000ms
#include <stdio.h>
int a[9][9];
int place(int x, int y) //二者分别是数组对应的行地址和列地址,取值为0-8
{
int up, down, left, right;
int i,j;
//算出同色格范围
up=x/3*3;
down=up+3;
left=y/3*3;
right=left+3;
//以下分三种情况判断是否在x,y对应的位置放这个数,如果不可以放,返回0,如果可以放,返回1,会进一步迭代
for(i=0;i<9;i++){
if(a[x][y]==a[i][y] && i!=x && a[i][y]!=0)//行检查
return 0;
}
for(i=0;i<9;i++){
if (a[x][y]==a[x][i] && i!=y && a[x][i]!=0)//列检查
return 0;
}
for(i=up;i<down;i++)//同色9宫格的情况
{
for(j=left;j<right;j++)
if(i!=x || j!=y)//不是自己即可
{
if(a[i][j]==a[x][y] && a[i][j]!=0)
return 0;
}
}
return 1;
}
void backtrack(int t)//第几个格子
{
int i,j;
int x,y;
if(t==81)
{
for(i=0;i<9;i++)
{
for(j=0;j<9;j++)
printf("%d",a[i][j]);
putchar('\n');
}
}
else
{
x=t/9;
y=t%9; //将这个转换为相应的数组行坐标和列坐标
if(a[x][y]!=0)
{
backtrack(t+1);
}
else
{
for(i=1;i<10;i++)
{
a[x][y]=i;
if(place(x,y)==1)
backtrack(t+1);//探索在此基础上后续的解空间
a[x][y]=0;//还要撤回 尝试该点的其余可能
}
}
}
}
int main()
{
char str[9][9];
int i,j;
for(i=0;i<9;i++)
gets(str[i]);
for(i=0;i<9;i++)
for(j=0;j<9;j++)
a[i][j]=str[i][j]-'0';
backtrack(0);
return 0;
}
练习2:排列数字
今有7对数字:两个1,两个2,两个3,…两个7,把它们排成一行。
要求,两个1间有1个其它数字,两个2间有2个其它数字,以此类推,两个7之间有7个其它数字。如下就是一个符合要求的排列:17126425374635当然,如果把它倒过来,也是符合要求的。请你找出另一种符合要求的排列法,并且这个排列法是以74开头的。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<stdlib.h>
#include<algorithm>
#include<cmath>
using namespace std;
int arr[16]={0};
int dfs(int n)
{
if(n > 6) return 1;//因为7距离已经确定,所以最大距离就是6
if(n == 4) n++;//因为当n=4,两者距离就是4,4的位置已经确定,所以跳过
int i=3;
for(;i <=14 ;i++)
{
if(i == 7 || i == 9) continue;//下标7,9位置值已经定了
if(i+n+1 <=14 && arr[i]==0 &&arr[i+n+1]==0)//保证两个位置都没有数据
{
arr[i] = arr[i+n+1] = n;
if(dfs(n+1))//继续在此基础上探索下一个即n+1距离解空间
return 1;
arr[i] = arr[i+n+1] = 0;//恢复上一次dfs状态,探索n的新可能
}
}
return 0;
}
int main()
{
arr[1] = 7,arr[2] = 4;//因为题目上已经给出两个开头,可以推出后面两个
arr[9] = 7,arr[7] = 4;
dfs(1);//先确定两个1
for(int i = 1;i < 16;i++ )
{
cout <<arr[i];
}
cout <<"\n";
return 0;
}