码迷,mamicode.com
首页 > 其他好文 > 详细

回溯法

时间:2016-07-05 14:12:08      阅读:171      评论:0      收藏:0      [点我收藏+]

标签:

回溯法(backtracking):递归地构造和枚举可能的情况,同时排除不必要的枚举,检查所有可能的解,这就是回溯法的思路

如果让我来评价的话,这种思路真的是很简单很暴力。但是往往很有效。

一、经典模型:八皇后问题:

在棋盘上放置8个皇后,棋盘为8*8,使它们互不攻击,每个皇后的攻击范围为同行同列和同对角线,找出所有的解

思路:

  1. 我们不能直接枚举所有可能的情况,因为直接枚举的解答树会有C864=4.426*10^9个节点,这是一定会爆的。
  2. 既然同行同列都会攻击,那么我们可以用cur表示放置的行数,递归地放置0-7行,这样可以避免对行的枚举,如果放置成功,那么这一定是一个解
  3. 估算计算量:如果说我们在不重复的行数放置皇后,那么计算量为8!=40320。这是可接受的
  4. 实现:如果我们用二维数组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实现如下:

  1. 从第二个位置填数字
  2. 如果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 }
View Code

技术分享

 

回溯法

标签:

原文地址:http://www.cnblogs.com/luruiyuan/p/5640769.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!