回溯法的本质:1. 在所有可能的情况中前行,具体的前行方式自己定,但前行方式必须确保:a.每前行一步之后当状态不能满足各种条件时,能回溯到最近的满足各种条件的某一步,这里常用的方法有递归,b.所有的情况都能考虑到,c.每一种情况只考虑一次; 2. 每前行一步需要做的事:a.要把这一步的所有情况都枚举考虑,并把这一步的每一种情况的状态修改为前行一步之后应变成的状态;3. 在每前行一步之后:如状态继续满足各种条件(注意这里的继续2字,因为它前一步的状态满足各种条件。也就是说最初的第一步必须从满足各种条件的状态开始前行,这就保证了每前行一步之前的状态都满足各种条件),则继续前行,不满足,则停止。
回溯法经典利用实例:8皇后问题。这里只说明8*8放8个皇后的问题。具体对照上面回溯法的本质来对号入座地分析下面的算法,就不再仔细说了。请大家一步步对照着分析一下。分析之后,相信大家对回溯法能有一个比较深刻的理解。
#include <stdio.h> #include <math.h> #include <stdlib.h> int sum=0;/*记录方案的个数*/ int Place(int k,int *x) /*判断新加入的皇后所在的位置 k 是否与其他皇后的位置冲突,此函数用来作为是否进行剪枝的判断条件*/ { int j; //(abs(k-j) == abs(x[j]-x[k])) //45度线判断。即abs(x1 - x2) = abs(y1 - y2)。y = abs(x) //因为算法是从第1行到第2行再到第3行...直到第n行考虑的, //因此只要考虑1到k行中,对角线和列上有没有皇后就行 for(j=1; j<k; j++) if( (abs(k-j) == abs(x[j]-x[k])) || (x[j]==x[k]) )/*x[i]表示皇后 i 放在棋盘的第 i 行的第x[i]列*/ return 0;/*能攻击到其他皇后,返回 0 */ return 1;/*不能攻击到其他皇后,返回 1 */ } /** *t表示第几皇后 */ void Backtrack(int t,int n,int *x) /*递归回溯求解*/ { int i; if(t>n) { sum++; /*输出一个方案*/ printf("方案%d:",sum); for(i=1; i<=n; i++) printf("(%d,%d) ",i,x[i]); printf("/n"); } else //将第t行第1到n列相继放上皇后。再考虑放或不能放之后的操作。 for(i=1; i<=n; i++) { x[t]=i;/*x[t]表示皇后 t 放在棋盘的第 t 行的第x[t]列*/ //如果新加入第t个皇后所在的位置 i 没有与其他皇后的位置冲突, //则进入解空间子树,试探第t+1个皇后的位置; //否则不进入子树,舍弃该段子树,即进行剪枝 if(Place(t,x)) Backtrack(t+1,n,x); } } main() { int n,*x; int i; printf("请输入皇后的个数:"); scanf("%d",&n); x=(int *)malloc((n+1)*sizeof(int));/*x[0]未使用*/ printf("以下每个方案都表示出了%d个皇后在棋盘上的坐标/n/n",n); Backtrack(1,n,x);/*回溯求解*/ printf("总共有%d种方案/n",sum); } //回溯法总结,本质:利用递归来达到回溯的目的。
