回溯法
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了回溯法相关的知识,希望对你有一定的参考价值。
回溯法(backtracking):递归地构造和枚举可能的情况,同时排除不必要的枚举,检查所有可能的解,这就是回溯法的思路
如果让我来评价的话,这种思路真的是很简单很暴力。但是往往很有效。
一、经典模型:八皇后问题:
在棋盘上放置8个皇后,棋盘为8*8,使它们互不攻击,每个皇后的攻击范围为同行同列和同对角线,找出所有的解
思路:
- 我们不能直接枚举所有可能的情况,因为直接枚举的解答树会有C864=4.426*10^9个节点,这是一定会爆的。
- 既然同行同列都会攻击,那么我们可以用cur表示放置的行数,递归地放置0-7行,这样可以避免对行的枚举,如果放置成功,那么这一定是一个解
- 估算计算量:如果说我们在不重复的行数放置皇后,那么计算量为8!=40320。这是可接受的
- 实现:如果我们用二维数组C记录状态(condition),vis[][]数组记录被占用的列、主对角线(左上到右下)、副对角线(右上到左下),递归放置即可
实现如下:此代码找出并打印n皇后问题的解
1 //找出并打印n皇后问题的解 2 #include<cstdio> 3 #include<cstring> 4 int tot=0,n,vis[3][100],C[10];//tot为解得总数,n为皇后个数 ,C记录地图,下标为行数,数组内容为列数 5 6 void search(int cur){ 7 if(cur==n){ 8 tot++; 9 printf("\\n"); 10 for(int i=0;i<n;i++){ 11 for(int j=0;j<n;j++) 12 printf("%c ",j!=C[i]?‘*‘:‘Q‘); 13 printf("\\n"); 14 } 15 printf("\\n"); 16 } 17 else for(int i=0;i<n;i++){ 18 if(!vis[0][i] && !vis[1][cur+i] && !vis[2][cur-i+n]){ 19 C[cur]=i; 20 vis[0][i]=vis[1][cur+i]=vis[2][cur-i+n]=1; 21 search(cur+1); 22 C[cur]=i; 23 vis[0][i]=vis[1][cur+i]=vis[2][cur-i+n]=0; 24 } 25 } 26 } 27 int main(){ 28 tot=0,scanf("%d",&n); 29 memset(vis,0,sizeof(vis)); 30 memset(C,0,sizeof(C)); 31 search(0); 32 printf("%d",tot); 33 }
如果只找出数目,不打印棋盘,实现如下
其中整个C数组都去掉了
1 #include<cstdio> 2 #include<cstring> 3 int tot=0,n,vis[3][20];//tot为解得总数,n为皇后个数 ,C数组省去了 4 5 void search(int cur){ 6 if(cur==n)tot++; 7 else for(int i=0;i<n;i++){ 8 if(!vis[0][i] && !vis[1][cur+i] && !vis[2][cur-i+n]){ 9 vis[0][i]=vis[1][cur+i]=vis[2][cur-i+n]=1; 10 search(cur+1); 11 vis[0][i]=vis[1][cur+i]=vis[2][cur-i+n]=0; 12 } 13 } 14 } 15 int main(){ 16 tot=0,scanf("%d",&n); 17 memset(vis,0,sizeof(vis)); 18 search(0); 19 printf("%d",tot); 20 }
二、素数环(UVa 524 PE了9次我去):
题目简述:输入一个数字n,把1-n的组成一个环,要求相邻两个数字之和为素数,其中n满足(0<n<=16), 输出从1开始,逆时针排列,每种环恰好输出一次
详细要求见传送门:UVa 524
思路:用回溯法,用数组a存储构造的字符串,vis数组记录构造的数字是否已经填入,用prime数组来打素数表,first用来判断输出格式,例如n==7时没有答案,Case语句不能输出
dfs实现如下:
- 从第二个位置填数字
- 如果cur==n,再判断最后一个数字和第一个数字的和能否构成素数,如果能,按指定格式输出。
实现如下:注意如何避免PE,下面的实现中有三处格式控制,防止PE
1 #include<cstdio> 2 #include<cmath> 3 int kase,n=20,prime[40],a[20],vis[20],first;//vis[i]记录 第i个数字是否被填在a数组中,first记录是否打印Case语句 4 bool isPrime(int x){ 5 if(x<2||(x%2==0)&&x!=2)return false; 6 if(x==2||x==3||x==5)return true; 7 for(int i=3,len=(int)sqrt(1.0*x)+1;i<=len;i++) 8 if(x%i==0)return false; 9 return true; 10 } 11 void dfs(int cur){ 12 if(cur==n && prime[a[0]+a[n-1]]){//打印方案 13 if(first){ 14 printf("Case %d:\\n",++kase);//这有格式 15 first=0;//第一次打印 16 } 17 for(int i=0;i<n;i++){ 18 printf("%d",a[i]); 19 printf("%c",i<n-1?‘ ‘:‘\\n‘);//这有格式 20 } 21 } 22 else for(int i=2;i<=n;i++){//这里是因为第一个数字必须是1,所以 1 不变,填后面 23 if(!vis[i] && prime[i+a[cur-1]]){ 24 vis[i]=1;//设置标志 25 a[cur]=i;//把数字i填入到a[cur]中 26 dfs(cur+1); 27 vis[i]=0;//清除标志 28 } 29 } 30 } 31 int main(){ 32 //初始化 33 for(int i=0;i<20;i++)a[i]=i+1; 34 for(int i=0;i<40;i++)prime[i]=isPrime(i);//打素数表 35 while(scanf("%d",&n)!=EOF){ 36 first=1; 37 if(kase)puts("");//这里有格式 38 dfs(1);//回溯法,这里不是0 39 } 40 }
以上是关于回溯法的主要内容,如果未能解决你的问题,请参考以下文章