目录
一、实验目的
1. 掌握回溯法的基本思想
基本思想:在回溯法中,每次扩大当前部分解时,都面临一个可选的状态集合,新的部分解就通过在该集合中选择构造而成。这样的状态集合,其结构是一棵多叉树,每个树结点代表一个可能的部分解,它的儿子是在它的基础上生成的其他部分解。树根为初始状态,这样的状态集合称为状态空间树。
2. 掌握相关问题的动态规划算法
(1) n-皇后问题
(2) 子集和数问题
3. 验证、巩固和补充课堂讲授的理论知识。
二、实验内容
1.n-皇后问题
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n 皇后问题要求在 n×n 的棋盘上放置n个皇后,使皇后彼此之间不能相互攻击,既任意的两个皇后都不在同一行、同一列或同一条对角线上。
2.子集和数问题
已知n个不同正整数wi(0<=i<=n-1)的集合,求该集合的所有满足条件的子集,使得每个子集中的正整数之和等于另一个给定的正整数M。
三、实验结果及分析
主要包括算法设计思路,详细设计、调试分析、运行结果以及算法的时间复杂度分析等。
1.算法设计
(1)n-皇后问题
首先判定当前位置( i , Q [ i ] ) (i, Q[i])(i,Q[i])是否可以放置皇后,要保证同列、对角线上没有被放置其他皇后,(这里不去判断同行的原因是,在寻找位置时是按行顺序进行的,所以每一个新皇后同行必然不存在其他皇后),按照坐标判断 Q [ j ] = = Q [ i ] Q[j]==Q[i]Q[j]==Q[i],a b s ( Q [ j ] − Q [ i ] ) = = a b s ( j − i ) abs(Q[j]-Q[i])==abs(j-i)abs(Q[j]−Q[i])==abs(j−i) 即可。判断当前位置是否满足条件,满足就进入下一个行皇后的放置;遍历下一行满足条件的位置,如果当前行中所有位置都不满足条件,那么进行回溯,在上一行继续向后寻找位置重新放置皇后。判断结束的标志,当回溯向前遍历了所有行,则结束程序。
(2)子集和数问题
对于数组中任意一个元素,先将其放入结果集中,如果当前和不超出给定和,那就继续考察下一个元素,如果超出给定和,则舍弃当前元素。如此往复,直到找到所有可行解。
2.思路分析
(1)n-皇后问题
step1 :从第1行的第1列位置开始,依次安排第2,3,4皇后的位置,并调用Place()函数对个皇后的位置进行判断;
step2:若判断通过则该位置即为皇后所在位置;若判断错误则该位置不是皇后所在的合适位置,便更改当前皇后所在位置,知道判断通过或当前行位置已全部遍历;
step3:若当前行的所有位置都已被判断且均判断失败,则回溯到上一行修改上一行皇后所在位置再重复上述过程。
(2)子集和数问题
step1:对输入的数据进行排序以方便后续的操作实现;
step2:遍历输入的数据,将数据中的元素依次放入结果集合并计算和;
step3:若当前和不超过给定和,则继续遍历下一元素;若超出给定和,则放弃当前数据元素,遍历下一元素;直到找到所有可行解。
3.核心代码
(1)n-皇后问题
int cnt=0;
int r[100][100];
int Place(int k,int i,int x[]){
int j;
for(j=0;j<k;j++){
if((x[j]==i)||(abs(x[j]-i)==abs(j-k))) return 0;
}
return 1;
}
void NQueens(int k,int n,int x[]){
int i,j=0;
for(i=0;i<n;i++){
if(Place(k,i,x)){
x[k]=i;
if(k==n-1){
for(i=0;i<n;i++) r[cnt][i]=x[i];
cnt++;
}
else NQueens(k+1,n,x);
}
}
}
(2)子集和数问题
void Sort(int w[],int n){
int i,j;
for(i=0;i<n;i++){
for(j=i;j<n-1;j++){
if(w[j]>w[j+1]){
int temp=w[j];
w[j]=w[j+1];
w[j+1]=temp;
}
}
}
}
void SumofSub(int k,int s,int r,int w[],int x[],int M,int n,int y[]){
int i,j,l;
int z[100];
for(i=1;i>=0;i--){
x[k]=i;
s+=x[k]*w[k];
r-=w[k];
if(s==M){
count++;
for(j=0;j<n;j++){
for(l=0;l<n;l++){
if(y[l]==w[j]){
z[l]=x[j];
break;
}
}
}
printf("%d ",z[0]);
for(j=1;j<n;j++)
printf("%d ",z[j]);
printf("\n");
}
else if((s+r>=M)&&(s+w[k+1]<=M)){
SumofSub(k+1,s,r,w,x,M,n,y);
}
s-=x[k]*w[k];
r+=w[k];
}
}
4.测试样例设计
(1)n-皇后问题
测试样例1
输入 | 4 |
输出 | 2 1 3 0 2 2 0 3 1 |
(2)子集和数问题
测试样例1
输入 | 4 31 11 13 24 7 |
输出 | 1 1 0 1 0 0 1 1 |
5.运行结果(截图)
(1)n-皇后问题
(2)子集和数问题
6.难点分析
(1)Point1:n皇后问题中的难点主要在于对各个皇后位置合法性的判断中,需要提前概括出各个皇后位置合法性判断的条件,也就是约束条件的代码表达方式;
(2)Point2:子集和数问题中的难点在于计算出所有可行解。当计算出第一可行解后,再计算后续的可行解则需要将第一可行解中的部分元素舍弃。而对于舍弃的元素的判断则是此问题的难点。
7.算法时间复杂度分析
(1)n-皇后问题
时间复杂度:O(n3)
(2)子集和数问题
时间复杂度:O(n2)
四、总结
通过此次的实验,对回溯法的算法思想有了更深的了解。也就是不断遍历数据中的相关元素,当遍历到某一元素时发现该元素不符合问题的可行解则终止继续判断,转而遍历下一元素。若元素全部被遍历过后仍然为找到可行解,则回溯到上一节点并修改上一节点的选择。再遍历数据元素。以此反复进行,直到全部遍历回溯完成或找到全部的可行解。同时也明白了回溯法的难点更在于对回溯条件的选取。选取一个合适的回溯条件将大大提升程序效率。