问题背景1
设有n件工作分配给n个人。将工作i分配给第j个人所需要的费用为cij。试设计一个算法,为每个人分配1件不同的工作,并使总费用达到最小。
解题思路1
解空间
解空间为{x1,x2,x3,······,xn},其中xi=1,2,3,4···,n,表示第i个人安排的工作号。
剪枝方法
算法中设置了一个数组,用来存放工作的完成状态,当工作完成时设为1,否则设为0。
代码实现1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <stdio.h> #define n 3 int price[n][n] = {10 ,2 ,3 ,2 ,3 ,4 ,3 ,4 ,5 };int minprice = 10000 ;int tempprice;int a[n];bool ok (int t) { int i; for (i = 0 ;i < t;i ++){ if (a[t] == a[i]){ return false ; } } return true ; } void nfs (int t) { int i; if (t == n){ if (tempprice < minprice){ minprice = tempprice; } return ; } for (i = 0 ;i < n;i ++){ a[t] = i; tempprice += price[t][i]; if (tempprice < minprice && ok (t)){ nfs (t + 1 ); } tempprice -= price[t][i]; } } int main () { nfs (0 ); printf ("%d\n" ,minprice); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> using namespace std;#define N 1000 int cost[N][N]; int isC[N] = {0 }; int n;int scost; void Backstrack (int i, int c) { if (c > scost) return ; if (i == n) { if (c < scost) scost=c; return ; } for (int j=0 ;j<n;j++) { if (isC[j]==0 ) { isC[j]=1 ; Backstrack (i+1 , c+cost[i][j]); isC[j]=0 ; } } } int main () { cin>>n; for (int i=0 ;i<n;i++) { for (int j=0 ;j<n;j++) { cin>>cost[i][j]; } } scost = N; Backstrack (0 ,0 ); cout<<scost; return 0 ; }
问题背景2
现有n种不同形状的宝石,每种宝石有足够多颗。欲将这些宝石排列成m行n列的一个矩阵,m<=n,使矩阵中每一行和每一列的宝石都没有相同的形状。试设计一个算法,计算出对于给定的m和n,有多少种不同的宝石排列方案。
avatar
解题思路2
这个题目基本思想是 利用回溯法,对于 m 行 n 列, 本质上就是一个二维数组, 我们可以将问题的解写成 x[1],x[2],x[3] … x[m*n], 那么对于每个点 x[i] 的取值实际上是 [1, n], 套用回溯法的算法框架,这里的 约束条件 ,就是同行,同列 没有相同的取值, 并且这里没有优化目标,只要类似 N后问题找出所有解就可以了。
另外,回溯时候的边界条件,需要特别注意一下,由于我们把二维数组当作一维数组进行实现, 下标从 0 开始, 理论上到 m*n-1就应该结束了。
代码实现2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <stdio.h> #define m 3 #define n 3 int a[m][n];int count = 0 ;bool ok (int x,int y) { int i; for (i = 0 ;i < x;i ++){ if (a[i][y] == a[x][y]){ return false ; } } return true ; } void nfs (int x,int y) { int i; int temp; for (i = y;i < n;i ++){ temp = a[x][y]; a[x][y] = a[x][i]; a[x][i] = temp; if (ok (x,y)){ if (x == m - 1 ){ if (y == n - 1 ){ count ++; return ; }else { nfs (x,y + 1 ); } } else { if (y == n - 1 ){ nfs (x + 1 ,0 ); }else { nfs (x,y + 1 ); } } } temp = a[x][y]; a[x][y] = a[x][i]; a[x][i] = temp; } } int main () { int i,j; for (i = 0 ;i < m;i ++){ for (j = 0 ;j < n;j ++){ a[i][j] = j + 1 ; } } nfs (0 ,0 ); printf ("%d\n" ,count); return 0 ; }
问题背景3
话说大诗人李白,一生好饮。幸好他从不开车。一天,他提着酒壶,从家里出来,酒壶中有酒2斗。他边走边唱: 无事街上走,提壶去打酒。 逢店加一倍,遇花喝一斗。 这一路上,他一共遇到店5次,遇到花10次,已知最后一次遇到的是花,他正好把酒喝光了。 请你计算李白遇到店和花的次序,可以把遇店记为a,遇花记为b。则:babaabbabbabbbb 就是合理的次序。像这样的答案一共有多少呢?请你计算出所有可能方案的个数(包含题目给出的)。
解题思路3
已知最后一种情况,则只要填充前面14次即可。回溯的条件判断越严谨,效率越高,所以不要把所有的条件放在最后一次填充时判断。而是每次填充时,判断一下酒量和店,花的次数是否符合。
代码实现3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 #include <stdio.h> #include <math.h> int main () { int i,j,k; int num,temp; int flower,shop,wine; int count = 0 ; for (num = 0 ;num < pow (2 ,14 );num ++){ flower = 0 ;shop = 0 ;wine = 2 ; temp = num; for (i = 0 ;i < 14 ;i ++){ if (temp % 2 ){ flower ++; wine --; }else { shop ++; wine *= 2 ; } temp /= 2 ; } if (wine == 1 && flower == 9 && shop == 5 ){ count ++; } } printf ("%d.\n" ,count); return 0 ; } #include <stdio.h> int count = 0 ;int flower = 0 ;int shop = 0 ;int wine = 2 ;void nfs (int n) { if (n == 15 ){ if (flower == 9 && shop == 5 && wine == 1 ){ count ++; } return ; } if (flower <= 9 && shop <= 5 ){ flower ++; wine --; nfs (n + 1 ); wine ++; flower --; } shop ++; wine *= 2 ; nfs (n + 1 ); wine /= 2 ; shop --; } int main () { nfs (1 ); printf ("%d.\n" ,count); return 0 ; } #include <stdio.h> #include <math.h> #include <string.h> int a[14 ];int count=0 ;void backtrace (int t,int s,int f,int c) { if (s>5 || f>9 || c<=0 ) return ; if (t==14 ){ if (s==5 && f==9 && c==1 ) { count++; } return ; } else { a[t]=1 ; backtrace (t+1 ,s+1 ,f,c*2 ); a[t]=0 ; backtrace (t+1 ,s,f+1 ,c-1 ); } } int main () { backtrace (0 ,0 ,0 ,2 ); printf ("%d" ,count); return 0 ; }
问题背景4
假设某国家发行了n种不同面值的邮票,并且规定每张信封上最多只允许贴m张邮票。连续邮箱问题要求对于给定的n和m,给出邮票面值的最佳设计,在1张信封上贴出从邮资1开始,增量为1的最大连续邮资区间。 例如当n=5,m=4时,面值为1,3,11,15,32的5种邮票可以贴出邮资的最大连续区间是1到70。
解题思路4
每张信封最多贴m张邮票,也就是说可能贴了m张,也可能贴了0张、1张等等。为了便于计算,我们可以把未贴满m张邮票看作贴了x张邮票和m-x张面值为0的邮票,从而构造一棵完全多叉树。若求所有可能的组合情况,解空间是(n+1)^m。 以n=5,m=4为例,解空间为完全多叉树,图中只以一条路径为例:
实际求解需要对其进行剪枝:解的约束条件是必须满足当前解的邮资可以构成增量为1的连续空间,所以在搜索至树中任一节点时,先判断该节点对应的部分解是否满足约束条件,也就是判断该节点是否包含问题的最优解,如果肯定不包含,则跳过对以该节点为根的子树的搜索,返回上一层的节点,从其他子节点寻找通向最优解的路径;否则,进入以该节点为根的子树,继续按照深度优先策略搜索。 求解过程:
读入邮票面值数n,每张信封最多贴的邮票数m
读入邮票面值数组nums[],除了正常面值,再加入一个值为0的面值
循环求取区间最大值maxValue,maxValue初始设为0 ① 从0张邮票开始搜索解空间,如果当前未达到叶节点,且过程值temp=temp+nums[i]未达到当前记录的区间最大值maxValue,则继续向下搜索 ② 若超过了区间最大值maxValue,则当前面值不是可行解,计算下一个面值nums[i+1]。若循环结束,当前节点的所有面值都无法满足,则说明再往下搜索也不可能有可行解,这个时候回溯到上一节点 ③ 若当前已搜索到叶节点,判断当前路径下的解temp是否满足比当前记录的区间最大值maxValue大1。若满足,则更新区间最大值maxValue;若不满足,回溯到上一节点
重复步骤3直到没有满足当前区间最大值maxValue+1的可行解,则当前记录的maxValue就是区间最大值
代码实现4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 #include <stdio.h> #define n 6 #define m 4 int a[n] = {0 ,1 ,3 ,11 ,15 ,32 };int flag = 1 ;int money;int num; void nfs (int t) { int i; if (flag == 0 ){ return ; } if (t == m){ if (money == num) flag = 0 ; return ; } for (i = 0 ;i < n;i ++){ money += a[i]; if (money <= num){ nfs (t + 1 ); } money -= a[i]; } } int main () { num = 1 ; while (1 ){ flag = 1 ; money = 0 ; nfs (0 ); if (flag) break ; num ++; } printf ("%d.\n" ,num - 1 ); return 0 ; } #include <iostream> #include <string.h> using namespace std; int n ,m ; int x[100 ]; int bestx[100 ];int y[10000 ]; int maxint; int maxl; int maxvalue; void backTrace (int i ,int r) { int z[10000 ]; for (int j = 0 ; j <= x[i-2 ]*(m-1 ) ; j++) { if (y[j] < m) { for (int k = 1 ; k <= m - y[j] ; k++) { if (y[j] + k < y[j + x[i-1 ] * k] ) { y[j + x[i-1 ]* k ] = y[j] + k; } } } } while (y[r] < maxint ) { r++; } if (i > n) { if (r - 1 > maxvalue) { maxvalue = r - 1 ; for (int p = 1 ; p <= n ; p++) { bestx[p] = x[p]; } return ; } } for (int k = 1 ; k <= maxl; k++) { z[k] = y[k]; } for (int j = x[i-1 ] + 1 ; j <= r ; j++) { x[i] = j ; backTrace ( i+1 , r); for (int k = 1 ; k <= maxl; k++) { y[k] = z[k]; } } } void process () { while (cin >> n >> m) { maxint = 32767 ; maxl = 1500 ; maxvalue = 0 ; memset (x , 0 , sizeof (x)); for (int i = 1 ; i <= maxl ; i++) { y[i] = maxint; } y[0 ] = 0 ; x[1 ] = 1 ; backTrace (2 ,1 ); cout << maxvalue; } } int main (int argc,char * argv[]) { process (); getchar (); return 0 ; }
问题背景5
还记得童话《卖火柴的小女孩》吗?现在,你知道小女孩有多少根火柴,请找出一种能使用所有火柴拼成一个正方形的方法。不能折断火柴,可以把火柴连接起来,并且每根火柴都要用到。
输入为小女孩拥有火柴的数目,每根火柴用其长度表示。输出即为是否能用所有的火柴拼成正方形。
示例 1:
输入: [1,1,2,2,2]
输出: true
解释: 能拼成一个边长为2的正方形,每边两根火柴。
示例 2:
输入: [3,3,3,3,4]
输出: false
解释: 不能用所有火柴拼成一个正方形。
解题思路5
新建立一个长为4的列表存储火柴,因为这里使用的是回溯,所以火柴按由大到小的顺序存储,这样可以减少回溯可能。在火柴全部存储后,就可以判断列表中四个小列表之和是否相等,如果都相等,证明可以拼成正方形。
在写代码的时候,先判断输入数组中火柴的总和是否为0,这是数组里火柴能否拼成正方形的先决条件。其次,在运行代码时,新建列表中的小列表之和不可超过总和长度的1/4。利用这些条件可以使代码的时间和空间复杂度很大程度的优化。
代码实现5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <stdio.h> #define n 8 #define m 4 int len[n] = {1 ,8 ,3 ,7 ,5 ,4 ,3 ,5 };int sum[m] = {0 };int flag = 0 ;int edge = 0 ;bool ok () { int i; for (i = 0 ;i < m;i ++){ if (sum[i] != edge){ return false ; } } return true ; } void nfs (int t) { int i; if (flag) return ; if (t == n){ if (ok ()){ flag = 1 ; } return ; } for (i = 0 ;i < m;i ++){ sum[i] += len[t]; if (sum[i] <= edge){ nfs (t + 1 ); } sum[i] -= len[t]; } } int main () { int i; for (i = 0 ;i < n;i ++){ edge += len[i]; } if (edge % m != 0 ){ printf ("no.\n" ); return 0 ; } edge /= m; nfs (0 ); if (flag){ printf ("yes.\n" ); }else { printf ("no.\n" ); } return 0 ; }
问题背景6
小明看完电影《第39级台阶》,离开电影院的时候,他数了数视觉的台阶数,恰好是39级。 ? 站在台阶前,他突然又想起一个问题:如果我每一步只能迈上1个或2个台阶,先迈左脚,然后左右交替,最后一步迈右脚,也就是说一共要迈偶数步。那么,上完39级台阶,有多少种不同的上法呢? ? 请利用计算机的优势,帮助小明寻找答案。
解题思路6
递归回溯不断模拟1、2步上台阶的过程
代码实现6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <stdio.h> int step = 0 ;int foot = 0 ;int count = 0 ;void nfs (int step) { if (foot % 2 == 0 && step == 39 ){ count ++; return ; } foot ++; step ++; if (step <= 39 ) nfs(step); step --; foot --; foot ++; step += 2 ; if (step <= 39 ) nfs(step); step -= 2 ; foot --; } int main () { nfs(0 ); printf ("%d.\n" ,count); return 0 ; } #include <stdio.h> int step = 0 ;int foot = 0 ;int count = 0 ;void nfs () { if (foot % 2 == 0 && step == 39 ){ count ++; return ; } foot ++; step ++; if (step <= 39 ) nfs(); step --; foot --; foot ++; step += 2 ; if (step <= 39 ) nfs(); step -= 2 ; foot --; } int main () { nfs(); printf ("%d.\n" ,count); return 0 ; }
问题背景7
在nn的棋盘上放置彼此不受攻击的n个皇后,按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在n n格的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。
解题思路7
针对给定问题,定义问题的解空间树
确定易于搜索的解空间结构
以深度优先方式搜索解空间,并且在搜索过程中永剪枝函数避免无效搜索。
下面给出一个三皇后问题的解空间树(3皇后问题无解),树中第i层的结点决定第i行皇后的摆放位置,均有三种不同的选择,便形成了三个孩子结点,但其中不包括不符合要求的布局。N皇后问题解空间树与三皇后问题解空间树类似。
代码实现7
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 #include <stdio.h> #include <math.h> #define n 8 int judge (int *a) { int i,j; for (i = 0 ;i < n;i ++){ for (j = i + 1 ;j < n;j ++){ if (a[i] == a[j] || abs (i - j) == abs (a[i] - a[j])){ return 0 ; } } } return 1 ; } int main () { int i,j,k; int num; int temp; int a[n]; int flag; int count = 0 ; for (num = 0 ;num < pow (n,n);num ++){ temp = num; for (i = 0 ;i < n;i ++){ a[i] = temp % n; temp /= n; } if (judge(a)){ for (i = 0 ;i < n;i ++){ printf ("%d " ,a[i]); } putchar ('\n' ); count ++; } } printf ("%d.\n" ,count); return 0 ; } #include <stdio.h> #include <math.h> #define n 8 int count = 0 ;int a[n];int judge (int t) { int i,j; for (i = 0 ;i <= t;i ++){ for (j = i + 1 ;j <= t;j ++){ if (a[i] == a[j] || abs (i - j) == abs (a[i] - a[j])){ return 0 ; } } } return 1 ; } void nfs (int m) { int i; if (m == n){ count ++; return ; } for (i = 0 ;i < n;i ++){ a[m] = i; if (judge(m)){ nfs(m + 1 ); } } } int main () { nfs(0 ); printf ("%d.\n" ,count); return 0 ; } #include <iostream> #include <math.h> using namespace std ; int Place (int k,int x[]) { for (int i=0 ;i<k;i++) if (x[i]==x[k] || abs (i-k)==abs (x[i]-x[k])) return 1 ; return 0 ; } int Queen (int n,int x[],int sum) { int k=0 ; int flag=0 ; while (k>=0 ) { x[k]++; while (x[k]<n && Place(k,x)==1 ) x[k]++; if (x[k]<n && k==n-1 ) { flag=1 ; sum++; for (int i=0 ;i<n;i++) cout <<x[i]+1 <<" " ; cout <<endl ; if (k<n && x[0 ]<n) { x[k--]=-1 ; continue ; } } if (x[k]<n && k<n-1 ) k+=1 ; else x[k--]=-1 ; } if (flag==0 ) cout <<"无解。" <<endl ; return sum; } int main () { int sum=0 ; int n=8 ; int x[8 ]; for (int i=0 ;i<n;i++) x[i]=-1 ; sum=Queen(n,x,sum); cout <<"解毕,共有 " << sum <<" 个解。" <<endl ; return 0 ; }