算法期末备考-第3练-回溯法(加强版)

算法期末备考-第3练-回溯法(加强版)

  这次练习主要是复习回溯法,之前一练主要还是学习了子集树与排序树的基本操作。

 

主要内容

  回顾知识:数字全排列(子集树、排序树)

  回溯法之加强版:素数环

  练习题:数字排序问题(蓝桥杯) + 39级台阶 + 数字排列(相邻之和为素数)

 


“温故而知新,可以为师矣“

 

全排列问题

【问题描述】

  给定数字n,请输出1~n的全部排列顺序。

  例如,n=3,输出:{1,2,3},{1,3,2},{2,1,3},{2,3,1},{3,1,2},{3,2,1}

【题解】

  利用两种回溯法的代表解决该问题。

 

  子集树做法:借助 vis[ ] 来标记其在集合中的元素。

  构建搜索树,其每一层相当于排列中的每一个位置。

  用参数step控制当前位置

  该位置填入什么数字,还需看vis[ ]是否被标记过

  若该数字已被集合选中,不做处理,则找另外的数字

  若该数字未被选中,则把该位置赋值上该数字。同时进行下一层搜索。

  当到达叶子结点时,则进行回溯。

  回溯时别忘记把当前位置的数字撤标记。

  对于当前位置来说又可以填入另外的数字。

其核心代码:

for( int i = 1 ; i <= n ; i ++ ){
    if( vis[i] == 0 ){
        vis[i] = 1 ;    A[step] = i ;
        dfs_subset( step + 1 );
        vis[i] = 0 ;
    }
}

排列树做法:

  由于排列必须时n个数,别忘记要初始化数字,直接给赋上值。(1~n)

  然后进行排列树的常规操作。

  构建搜索树,其每一层相当于排列中的每一个位置。

  用参数<S,E>控制,S表示当前位置,E表示结束位置。

  我们所需要的是对下标为S,即当前位置的位置分别与后面的数字交换操作。

  其目的就是为达到该位置 把全部数字在该位置轮流打头。

  到达叶子结点的条件是,当前位置S已经是结束位置E

核心代码:

for( int i = S ; i <= E ; i++ ){
    swap( B[S] , B[i] );
    dfs_Permutation( S+1 , E );
    swap( B[S] , B[i] );
}

具体代码:

 

 1 //DFS 利用子集树 和 排列树 实现数字全排列
 2 #include<cstdio>
 3 #include<algorithm>
 4 using namespace std;
 5 const int N = 20 ;
 6 int vis[N],n;
 7  8 int A[N] , B[N];
 9 void dfs_subset(int step){
10     if( step == n ){
11         for( int i = 0 ; i < n ; i++ ){
12             printf("%3d",A[i]);
13         }
14         putchar('
');
15     }
16     for( int i = 1 ; i <= n ; i ++ ){
17         if( vis[i] == 0 ){
18             vis[i] = 1 ;    A[step] = i ;
19             dfs_subset( step + 1 );
20             vis[i] = 0 ;
21         }
22     }
23 }
24 25 void dfs_Permutation( int S , int E ){
26     if( S == E ){
27         for( int i = 1 ; i <= n ; i++ ){
28             printf("%3d",B[i]);
29         }
30         putchar('
');
31         return ;
32     }
33     for( int i = S ; i <= E ; i++ ){
34         swap( B[S] , B[i] );
35         dfs_Permutation( S+1 , E );
36         swap( B[S] , B[i] );
37     }
38 }
39 int main()
40 {
41     puts("Please input N :");
42     scanf("%d",&n);
43 44     puts(" dfs_subset tree");
45     dfs_subset(0);
46 47     puts(" dfs_Permutation tree");
48     for( int i = 1 ; i <= n ; i++ ){
49         B[i] = i ;
50     }
51     dfs_Permutation(1,n);
52     return 0;
53 }
全排列

 


 

素数环

【题目描述】

  素数环是一个计算机程序问题,指的是将从1到n这n个整数围成一个圆环,若其中任意2个相邻的数字相加,结果均为素数,那么这个环就成为素数环。现在要求输入一个n,求n个数围成一圈有多少种素数环,规定第一个数字是1。

【小结】

  大家千万不要去新的服务器交题目。我都快自闭了。然后去了以前的服务器上交题过了。但是那个题目的数据有错,应该需要特判1,因为1本身不是素数。而那道题目的数据虽然涉及n=1的情况,但是答案却是1,答案应该是“-1”。

【题解】

  全排列的一个变种问题,其实就是在过程中加一个判断即可。在放数字判断前一个数与当前位置需要放的数之和是否为质数。因为是一个环,所以最后还需要加一个判断,即最后一个数与1相加是否为质数。

  总结:朴素全排列问题+过程中特判相邻数之和为质数+最后一个数与1相加为质数。

 

 1 //dfs解决素数环  hdu-1016
 2 // http://acm.hdu.edu.cn/showproblem.php?pid=1016
 3 #include<cstdio>
 4 #include<cstring>
 5 using namespace std;
 6 const int N = 1e3 +10 ;
 7  8 int n ;
 9 int a[N] ;
10 int vis[N] ;
11 bool is_prime[N] ;
12 13 void Init(){
14     for( int i = 2 ; i < N ; i++ ) {
15         bool f = true;
16         for (int j = 2; j * j <= i; j++) {
17             if (i % j == 0) {
18                 f = false;
19                 break;
20             }
21         }
22         if (f) is_prime[i] = true;
23 24     }
25     a[1] = 1 ;  vis[1] = -1 ;
26 }
27 28 void dfs_subset( int step ){
29     if( step == n + 1 ){
30         if( is_prime[ a[n] + 1 ] ){
31             for( int i = 1 ; i <= n ; i ++ ){
32                 printf("%d%c",a[i],i==n?'
':' ');
33                 //printf("%d ",a[i]);
34             }
35             //putchar('
');
36         }
37         return ;
38     }
39 40     for( int i = 2 ; i <= n ; i++ ) {
41         if (vis[i] == 0 && is_prime[i + a[step - 1]]) {
42             vis[i] = 1;     a[step] = i;
43             dfs_subset(step + 1);
44             vis[i] = 0;
45         }
46     }
47 }
48 int main()
49 {
50     Init() ;
51     int Case = 0 ;
52     while( ~scanf("%d",&n) ){
53         printf("Case %d:
",++Case);
54         if( n > 20 ){
55             printf("-1
");
56         }else if( n % 2 == 1 ){
57             printf("-1
");
58         }else{
59             dfs_subset( 2 );
60         }
61         puts("");
62     }
63     return 0 ;
64 }
素数环

 

练习题

 

数字排列问题

【题目描述】

今有7对数字:两个1,两个2,两个3,...两个7,把它们排成一行。 要求,两个1间有1个其它数字,两个2间有2个其它数字,以此类推,两个7之间有7个其它数字。如下就是一个符合要求的排列: 17126425374635 当然,如果把它倒过来,也是符合要求的。 请你找出另一种符合要求的排列法,并且这个排列法是以74开头的。 注意:只填写这个14位的整数,不能填写任何多余的内容,比如说明注释等。 744*7***

【题解】

直接全排列公式用上,同时要多增加一个标记数组,每个位置放一个数的同时相隔i+1的位置同时放。

然后就OK了。

 1 //74****4*7*******
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 const int N = 20 ;
 6 int vis[N] , a[N];
 7 void dfs( int step ){
 8     if( step == 14 ){
 9         for( int i = 1 ; i <= 14 ; i++ ){
10             printf("%d%c",a[i],i==14?'
':' ');
11         }
12         return ;
13     }
14 15     if( a[step] != -1 ) dfs( step + 1 );
16 17     for( int i = 1 ; i <= 6 ; i ++ ){
18         if( a[step] == -1 && vis[i] == 0 && step + i + 1 <= 14 && a[ step + i + 1] == -1 ){
19             vis[i] = 1 ;
20             a[step + i + 1] = a[step] = i ;
21 22             dfs( step + 1 );
23 24             a[step + i + 1] = a[step] = -1 ;
25             vis[i] = 0 ;
26         }
27     }
28 }
29 int main(){
30     for( int i = 1 ; i <= 14 ; i ++ ) a[i] = -1 ;
31     vis[4] = vis[7] = -1 ;
32     a[1] = 7 , a[9] = 7 ;
33     a[2] = 4 , a[7] = 4 ;
34     dfs( 3 ) ;
35     return 0 ;
36 }
数字排列问题

 


 

39级台阶

【题目描述】

  小明看完电影《第39级台阶》,离开电影院的时候,他数了数视觉的台阶数,恰好是39级。 站在台阶前,他突然又想起一个问题:如果我每一步只能迈上1个或2个台阶,先迈左脚,然后左右交替,最后一步迈右脚,也就是说一共要迈偶数步。那么,上完39级台阶,有多少种不同的上法呢? 请利用计算机的优势,帮助小明寻找答案。


【题解】

  利用递归来实现,从第0层往上走,上1级或2级。

  题目都给出来了,先迈左脚,最后迈出右脚,可以说明步数为偶数。


 1 #include<cstdio>
 2 using namespace std;
 3 int n = 39 ;
 4 int ans = 0 ;
 5 void dfs( int step , int k ){
 6     //定义结束条件
 7     if( step == 39 && k % 2 == 0 ){
 8         ans ++ ;
 9         return ;
10     }
11     //结束条件需要多加一条
12     if( step >= 40 ) return ;
13 14     //迈出一步,台阶增加1或2
15     dfs( step + 1 , k + 1 );
16     dfs( step + 2 , k + 1 );
17 }
18 //51167078
19 int main()
20 {
21     dfs( 0 , 0 );
22     printf("%d
",ans);
23     return 0;
24 }
39级台阶

 


 

数字排列问题

【题目描述】

将1到20排一列,要求每相邻两位数的和是质数,试求排列的种数。

【题解】

用排列树解决,弱化版的素数环问题。

预处理所有素数出来,用bool数组标识出来。

然后放每一个数的时候加上两数之和为素数即可。

只不过剪枝效果不太好,等个2~3分钟才出答案。。

 

 1 //dfs解决数字排列问题
 2  3 #include<cstdio>
 4 #include<cstring>
 5 #include<algorithm>
 6 using namespace std;
 7 const int N = 1e3 +10 ;
 8  9 int n ;
10 int a[N] ;
11 bool is_prime[N] ;
12 13 void Init(){
14     for( int i = 2 ; i < N ; i++ ) {
15         bool f = true;
16         for (int j = 2; j * j <= i; j++) {
17             if (i % j == 0) {
18                 f = false;
19                 break;
20             }
21         }
22         if (f) is_prime[i] = true;
23     }
24 }
25 26 int ans = 0 ;
27 void dfs_Permutation( int S , int E ){
28     if( S == E && is_prime[ a[E] + a[E-1] ]){
29         ans ++ ;
30         return ;
31     }
32     for( int i = S ; i <= E ; i++ ) {
33         if( S == 1 || ( is_prime[ a[i] + a[S-1] ] ) ){
34             swap( a[S] , a[i] );
35             dfs_Permutation( S + 1 , E );
36             swap( a[S] , a[i] );
37         }
38     }
39 }
40 int main()
41 {
42     Init() ;
43     n = 20 ;
44     for( int i = 1 ; i <= n ; i ++ ){
45         a[i] = i ;
46     }
47     dfs_Permutation( 1 , n );
48     printf("%d
",ans);
49     return 0 ;
50 }
数字排列问题(相邻之和为素数)
原文地址:https://www.cnblogs.com/Osea/p/12114854.html