C++ 基础算法 - 搜索与回溯算法
今天,我们将要学习第五个算法 - 搜索与回溯算法。
搜索
搜索,是有目的地进行穷举一个问题结果的范围,直至找出正确答案为止 ( 说白了就是穷举,但不意味着完全相同 )
搜索算法有很多种,比如深度优先搜索 ( DFS ),广度优先搜索 ( BFS ),A* 等
下面以一张迷宫图来看看搜索的过程 ( 这里利用 DFS 进行搜索 )
红色代表起点,白色代表空气,黑色代表墙,绿色代表终点
太过于简单的迷宫
相信所有人都能够很快地解出来吧?
还是非常简单的...我只试了亿次就过了
如果我们要让计算机来走迷宫要怎么办呢
因为输入地图比较麻烦,可以把地图变成这个样子
int Map[10][10]=
{
{1,1,1,1,1,1,1,1,1,1},
{1,2,1,0,1,0,1,0,0,1},
{1,0,1,0,0,0,1,0,1,1},
{1,0,1,0,1,0,1,0,0,1},
{1,0,1,0,1,0,1,1,0,1},
{1,0,1,0,1,0,0,0,0,1},
{1,0,0,0,1,0,1,1,0,1},
{1,1,1,1,1,0,1,0,0,1},
{1,0,0,0,0,0,1,3,0,1},
{1,1,1,1,1,1,1,1,1,1},
};//直接赋初始值
非常的直观,1 是墙,0 是空气,2 是起点,3 是终点
这里先展示出代码,代码的结果就是迷宫是否有解 ( YES 代表有解,NO 代表无解 ),接下来就以这个代码来进行讲解
#include<iostream>
#include<cstdio>
using namespace std;
bool data[10][10],win(false);
int Map[10][10]=
{
{1,1,1,1,1,1,1,1,1,1},
{1,2,1,0,1,0,1,0,0,1},
{1,0,1,0,0,0,1,0,1,1},
{1,0,1,0,1,0,1,0,0,1},
{1,0,1,0,1,0,1,1,0,1},
{1,0,1,0,1,0,0,0,0,1},
{1,0,0,0,1,0,1,1,0,1},
{1,1,1,1,1,0,1,0,0,1},
{1,0,0,0,0,0,1,3,0,1},
{1,1,1,1,1,1,1,1,1,1},
};
void search(int x,int y)
{
if(data[x][y]) return;
if(Map[x][y]==3)
{
win=true;
return;
}
data[x][y]=true;
if(Map[x+1][y]!=1&&!win) search(x+1,y);
if(Map[x-1][y]!=1&&!win) search(x-1,y);
if(Map[x][y+1]!=1&&!win) search(x,y+1);
if(Map[x][y-1]!=1&&!win) search(x,y-1);
}
int **in()
{
search(1,1);
if(win) printf("YES\n");
else printf("NO\n");
return 0;
}
不要看有整整 38 行代码,以后还有更长的,这里的赋值就占了一大半,所以也不是很长了
那么,要让机器去走迷宫,就要有一个出发点,一个目标,以及过程
search(1,1);目标也很明确,就是 3 的地方,搜索到它的地方后就表示成功了,即在递归中的返回操作
if(Map[x][y]==3)
{
win=true;
return;
}
过程也很简单,就是通过计算机进行上下左右的查找,能走则走,直至找到答案,就是向上下左右寻找可以走的点即可
if(Map[x+1][y]!=1) search(x+1,y);
if(Map[x-1][y]!=1) search(x-1,y);
if(Map[x][y+1]!=1) search(x,y+1);
if(Map[x][y-1]!=1) search(x,y-1);
例如,从起点出发,一直走到第一个路口前都没有其它的路 ( 除了来时的路,所以如果让程序能够让 4 个方向走时一定要规定不要往回走,否则就会卡死 ),到了第一个路口,先让程序像上走,发现除了来时的路,已经没有任何路了,所以程序都会回溯 ( 先不管怎么做到的,先知道这个概念 ) 回溯到上一个选择处,继续选择下一个路口走
如果按照这样的方法走,找到了目标就标记成功并退出即可,但是如果没有可行的路线,程序会怎么样呢
程序在没有选择时会进行回溯,退回到上一个选择并重新选择,但是,程序如果退回到了最开始的地方也没有路可走,就会直接退出
回溯
搜索有了基本的概念,接下来看看回溯是怎么做到的,我们知道,搜索没了选择就会回溯,目的是重新选择其它的路,产生新的不同的结果
可回溯的搜索中是利用递归实现的,所以回溯就是利用递归的性质回退到上一个能选择的路口中,但是这里的回溯不是很明显,因为这里只要返回并寻找新的路径即可,所以唯一可以讲的就是选择新路径,每一个不同的路口就是一个 if 语句,当一个路径口退出了,就会继续判断下一个路径是否可行,这样就无形之中做到了回溯这一点
其它
如何做到在遇到已经走过的路后不再继续走 ? 可以用一个数组来表示哪里已经走过,这样就可以少走弯路,加快速度 ( 其实这是必须的,要不然根本停不下来... ),还有在成功到达终点后不能直接返回,否则程序就会以为是回溯,要重新进行递归查找,虽然这样也能成功,但是会消耗不比消耗的时间,所以可以设置一个变量,记录是否成功查找到了,如果查找到了就不必继续搜索了,加速退出,当然这是视情况而定的,就比如有些是必须要搜索到底的
一本通平台讲解
1317 - 1318,1212 - 1222
1317:【例5.2】组合的输出
这道题和 1199:全排列 类似,只不过是将字母换成了数字
代码如下
#include<iostream>
#include<io**nip>//setw()函数所需的头文件
#include<cstdio>
using namespace std;
int n,r,a[30];
bool used[30];
void print()
{
int i;
for(i=1;i<=r;i++) cout<<setw(3)<<a;
//设置长宽还可以直接用printf更加简单
//可以将cout<<setw(3)<<a;
//替换成printf("%3d",a);
//两者都是具有同样效果的
printf("\n");
}
void search(int x)
{
int i;
for(i=a[x-1];i<=n;i++)//从上一次选取的值开始枚举
{
if(!used)//是否使用过
{
used=true;
a[x]=i;
if(x==r) print();//输出
else search(x+1);//继续查找
used=false;
}
}
}
int **in()
{
scanf("%d%d",&n,&r);
a[0]=1;
search(1);
return 0;
}
1318:【例5.3】自然数的拆分
这道题和 1200:分解因数 类似
#include<iostream>
#include<cstdio>
using namespace std;
int n,a[10001],sum=0;
void print(int m)
{
int i;
sum++;
printf("%d=",n);
for(i=1;i<m;i++) printf("%d+",a);
printf("%d\n",a[m]);
}
void search(int x,int m)
{
int i;
for(i=a[m-1];i<=x;i++)//枚举可行的数
{
if(i<n)
{
a[m]=i;
x-=i;
if(x==0) print(m);//分解成功就输出
else search(x,m+1);//继续分解
x+=i;
}
}
}
int **in()
{
int i;
for(i=0;i<=10000;++i) a=1;
scanf("%d",&n);
search(n,1);
return 0;
}
1212:LETTERS
这道题也很简单
#include<iostream>
#include<cstdio>
using namespace std;
int n,m,**xn=0,mov[4][2]={{0,1},{0,-1},{1,0},{-1,0}};//移动数组,用于一个点的延伸
bool num[26];//标记某个字母是否走过
char Map[30][30];
void search(int x,int y,int step)
{
int i,nx,ny;//NewX,NewY,用于确定新延伸的点
**xn=**x(step,**xn);
for(i=0;i<4;i++)
{
nx=x+mov[0];//找到新的点
ny=y+mov[1];
if(nx>=0&&nx<n&&ny>=0&&ny<m&&!num[Map[nx][ny]-'A'])
//判断是否出界或是否走过这个字母
{
num[Map[nx][ny]-'A']=true;//标记字母为走过
search(nx,ny,step+1);//继续寻找
num[Map[nx][ny]-'A']=false;//标记字母未走过
}
}
}
int **in()
{
int i,j;
scanf("%d%d",&n,&m);
for(i=0;i<n;i++) for(j=0;j<m;j++) cin>>Map[j];
num[Map[0][0]-'A']=true;//标记走过
search(0,0,1);//搜索
printf("%d\n",**xn);//输出
return 0;
}
1213:八皇后问题
八皇后问题是搜索中非常经典的一道题,在国际象棋中,一个皇后可以吃掉其上下左右和两条斜边延伸出的线上的棋子,题目要求的是在国际象棋中放置八个皇后,使得它们互相都吃不到对方 ( 一步以内,一步以外就无解了 )
代码如下
#include<iostream>
#include<cstdio>
using namespace std;
int a[9][9],b[9];
bool Map[20][3];
int sum;
void search(int num)
{
int i;
if(num==9)
{
sum++;
for(i=1;i<=8;i++) a[sum]=b;//标记
return;
}
for(i=1;i<=8;i++)
{
if(!Map[0]&&!Map[num+i][1]&&!Map[num-i+8][2])
{
Map[0]=true;
Map[i+num][1]=true;
Map[num-i+8][2]=true;//分别标记上下左右和斜边不能够放置了
b[num]=i;//记录第num个皇后放在哪里
search(num+1);//枚举下一个皇后的位置
Map[0]=false;
Map[i+num][1]=false;
Map[num-i+8][2]=false;//标记上下左右斜边可以放置了
}
}
}
int **in()
{
int k,i,j;
search(1);
for(k=1;k<=sum;k++)
{
printf("No. %d\n",k);//输出
for(i=1;i<=8;i++)
{
for(j=1;j<=8;j++)
{
if(a[k][j]==i) printf("1 ");
else printf("0 ");
}
printf("\n");
}
}
return 0;
}
你知道吗
八皇后问题总共有 92 种解
1214:八皇后
这里就直接上代码了
#include<iostream>
#include<cstdio>
using namespace std;
bool path[10][10];
int num=0;
bool check(int x,int y)//判断函数,用于判断点(x,y)处能否放置皇后
{
int i,j;
for(i=0;i<8;++i) if(path[x]) return false;
for(i=0;i<8;++i) if(path[y]) return false;
for(i=0;i<8;++i) for(j=0;j<8;++j) if(i-j==x-y&&path[j]) return false;
for(i=0;i<8;++i) for(j=0;j<8;++j) if(i+j==x+y&&path[j]) return false;
return true;
}
void dfs(int s,int test)
{
int i,j;
if(s==8)//放满了就输出
{
num++;
if(test==num) for(i=0;i<8;++i) for(j=0;j<8;++j) if(path[j]) printf("%d",j+1);
return;
}
for(i=0;i<8;++i)
{
if(check(s,i))
{
path[s]=true;//标记放了皇后
dfs(s+1,test);
path[s]=false;
}
}
}
int **in()
{
int n,x;
scanf("%d",&n);
while(n--)
{
scanf("%d",&x);
dfs(0,x);
num=0;
printf("\n");
}
return 0;
}
1215:迷宫
这道题类似于示例,这里就不讲了
#include<iostream>
#include<cstring>//memset函数所需的头文件
#include<cstdio>
using namespace std;
int mov[4][2]={{0,1},{1,0},{0,-1},{-1,0}},sx,sy,ex,ey,n;
char **p[101][101];
bool **p1[101][101],f;
void search(int x,int y)
{
int i,nx,ny;
if(x==ex&&y==ey)
{
f=true;//找到了目标
return;//返回
}
for(i=0;i<4;i++)
{
nx=x+mov[0];
ny=y+mov[1];
if(**p[nx][ny]=='.'&&**p1[nx][ny]==0&&nx>=0&&nx<n&&ny>=0&&ny<n)//符合
{
**p1[nx][ny]=1;
search(nx,ny);//继续寻找
}
}
}
int **in()
{
int i,j,k;
scanf("%d",&k);
while(k--)
{
memset(**p,0,sizeof(**p));//初始化函数
memset(**p1,0,sizeof(**p1));
scanf("%d",&n);
for(i=0;i<n;i++) for(j=0;j<n;j++) cin>>**p[j];
scanf("%d%d%d%d",&sx,&sy,&ex,&ey);//输入
f=false;
if(**p[sx][sy]=='#'||**p[ex][ey]=='#')//先行判断
{
printf("NO\n");
continue;
}
search(sx,sy);//搜索
if(f) printf("YES\n");
else printf("NO\n");
}
return 0;
}
1216:红与黑
也是很简单
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
char t;
int m,n,**p[1001][1001],mov[4][2]={{0,1},{0,-1},{1,0},{-1,0}},sum;
bool visited[1001][1001];
void search(int x,int y)//搜索
{
int i,nx,ny;
for(i=0;i<4;i++)
{
nx=x+mov[0];
ny=y+mov[1];
if(!visited[nx][ny]&&**p[nx][ny]==1&&nx>0&&nx<=n&&ny>0&&ny<=m)
{
visited[nx][ny]=true;
sum++;
search(nx,ny);
}
}
}
int **in()
{
int i,j,x,y;
while(true)
{
scanf("%d%d",&m,&n);
if(m==0&&n==0) break;
sum=1;
memset(visited,false,sizeof(visited));//初始化很重要
memset(**p,0,sizeof(**p));
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
cin>>t;
switch(t)
{
case '@':x=i;y=j;**p[j]=1;break;
case '.':**p[j]=1;break;
case '#':**p[j]=0;break;
}
}
}
visited[x][y]=true;//标记走过的点
search(x,y);
printf("%d\n",sum);
}
return 0;
}
1217:棋盘问题
这道题跟八皇后差不多
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int n,k,Map[20][20],cnt;
bool visited[20],flag;
char c;
void search(int x,int y)
{
int i,j;
if(y==k) cnt++;//达到数量就增加方案数
else
{
for(i=x;i<n;i++)
{
for(j=0;j<n;j++)
{
if(Map[j]&&!visited[j])
{
visited[j]=true;
search(i+1,y+1);//能放则放
visited[j]=false;
}
}
}
}
}
int **in()
{
int i,j;
while(true)
{
scanf("%d%d",&n,&k);
if(n==-1&&k==-1) break;
memset(Map,0,sizeof(Map));
memset(visited,false,sizeof(visited));//初始化
cnt=0;
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
{
cin>>c;
if(c=='#') Map[j]=1;
else Map[j]=0;
}
}
search(0,0);//搜索
printf("%d\n",cnt);
}
return 0;
}
1218:取石子游戏
这道题是博弈论,可以按照题目中给出的提示来做
假设石子数目为(a,b)且a >= b,如果[a/b] >= 2则先手必胜,如果[a/b]<2,那么先手只有唯一的一种取法。[a/b]表示a除以b取整后的值。
#include<iostream>
#include<cstdio>
using namespace std;
int **in()
{
int n,m;
while(true)
{
scanf("%d%d",&n,&m);
if(n==0&&m==0) break;//遇到0退出
if(n<m) swap(n,m);//保证n>m
if(n/m>=2)//如果n/m取整的值≥2先手必赢
{
printf("win\n");
continue;
}
int i=1,x;
while(n/m==1)//否则模拟两人的过程
{
x=n;
n=m;
m=x%n;
i++;
}
if(i%2==1) printf("win\n");
else printf("lose\n");
}
return 0;
}
1219:马走日
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int n,m,x,y,ans=0,mov[8][2]={{-2,1},{-2,-1},{-1,2},{-1,-2},{2,1},{2,-1},{1,2},{1,-2}};
bool **p[21][21];//这里的mov是马的移动数组
int search(int x,int y,int sum)
{
if(sum==n*m)//满足条件
{
ans++;
return 0;
}
int i,nx,ny;
for(i=0;i<8;i++)
{
nx=x+mov[1];
ny=y+mov[0];
if(**p[nx][ny]==0&&nx>=0&&nx<n&&ny>=0&&ny<m)
{
**p[nx][ny]=true;
search(nx,ny,sum+1);//寻找
**p[nx][ny]=false;
}
}
}
int **in()
{
int i,n;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d%d%d%d",&n,&m,&x,&y);
ans=0;
memset(**p,false,sizeof(**p));
**p[x][y]=true;
search(x,y,1);
printf("%d\n",ans);
}
return 0;
}
1220:单词接龙
这题也是利用深搜去找到较大的匹配方案
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
string str[101],s;
int r[101],n,**xn;
bool check(string x,string y,int k)//检查是否能够拼接
{
int i,l=x.size();
for(i=0;i<k;i++) if(x[l-k+i]!=y) return false;
return true;
}
void splicing(string &x,string y,int k)
//拼接函数,其中的&先不用管,只用知道用了它就可以直接更改到x本身而不是在函数中的x的值即可
{
int i,l=y.length();
for(i=k;i<l;i++) x+=y;
}
void search(string s)
{
int i,j,l=s.size();
**xn=**x(**xn,l);//取较大值
for(i=1;i<=n;i++)
{
if(r>=2) continue;
for(j=1;j<=str.size();j++)
{
if(check(s,str,j))//查看是否能够拼接
{
string t=s;
splicing(t,str,j);//拼接
if(t==s) continue;
r++;
search(t);//继续搜索
r--;
}
}
}
}
int **in()
{
int i;
scanf("%d",&n);
for(i=1;i<=n;i++) cin>>str;
cin>>s;//输入
search(s);//搜索
printf("%d\n",**xn);
return 0;
}
1221:分成互质组
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int a[10100],sum[10100],n;
bool check(int x)//检测这些数字是否互质
{
int i;
for(i=2;i<x;i++)
{
if(x%i==0)
{
if(sum) return false;
else sum++;
}
}
return true;
}
void search(int k,int x)//搜索
{
int i;
for(i=k;i<n;i++)
{
if(check(a))
{
a=-1;
k++;
search(k,a[k]);
}
}
return;
}
int **in()
{
int i,ans=0;
scanf("%d",&n);
for(i=0;i<n;i++) scanf("%d",&a);
for(i=0;i<n;i++)
{
if(a!=-1)
{
ans++;
memset(sum,0,sizeof(sum));//初始化很重要
search(i,a);//搜索
}
}
printf("%d\n",ans);
return 0;
}
1222:放苹果
这道题已经讲过了就不再讲了
#include<iostream>
#include<cstdio>
using namespace std;
int search(int x,int y)//搜索
{
if(x==0||y==1) return 1;
if(x<y) return search(x,x);
return search(x,y-1)+search(x-y,y);
}
int **in()
{
int t,n,k;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&k);
printf("%d\n",search(n,k));
}
return 0;
}
转载:感谢您阅览,转载请注明文章出处“来源从小爱孤峰知识网:一个分享知识和生活随笔记录的知识小站”。
链接:C++ 基础算法 - 搜索与回溯算法http://www.gufeng7.com/niaolang/1856.html
联系:如果侵犯了你的权益请来信告知我们删除。邮箱:119882116@qq.com
上一篇: C++ 基础算法 - 递推 & 递归
下一篇: C++ 结构体&指针