回溯问题+幂集、排列、子集和问题、八皇后问题
2014-12-04 16:20
417 查看
1.递归问题
递归里面用return了,那么函数遇到return 就不走了,是不是就不用归了?
这这个问题比如下面的代码:#include<iostream> using namespace std; void Function(int n) { if(n == 1) { cout<<"n="<<n<<endl; return; } Function(n-1); cout<<"n="<<n<<endl; } int main(){ Function(5); system("pause"); return 0; }输出的结果是1,2,3,4,5
也就是说return只返回当前调用它的函数
return 对当前函数来说是结束了,对调用它的父函数来说你这个函数执行完成了,父函数就会接着执行下一语句。
没想到父函数马上又遇到一个return,父函数结束了,对爷爷函数来说父函数执行完成了,爷爷函数就接着执行下一个语句
没想到。。。
没想到。。。
也就是说先调用Function(5)---Function(1),输出1,返回;再调用Function(2),输出2,再........
可以看出上面的过程有回溯的味道!回溯就是这样由递归自然形成的!
2.幂集问题
幂集可以用回溯解决任何一个数在子集中可以选择出现或者不出现
参见回溯法扫盲帖对回溯的描述
#include<iostream> using namespace std; void sub(int start,int num,int data[],bool label[]){ if(start==num){//此时是从0开始的,所以的==num或>=num,表示到叶子了 for(int i=0;i<num;i++){ if(label[i]){ cout<<data[i]<<" "; } } cout<<endl; return;//此时仅仅是结束了当前的函数,不会退出整个函数,不能少 } label[start]=true; sub(start+1,num,data,label); label[start]=false; sub(start+1,num,data,label); } int main(){ int num=3; int data[3]={1,2,3}; bool label[3]={false}; sub(0,num,data,label); system("pause"); return 0; }
可以看到还是按字典序输出的
当然还可以用二进制的方法,总有2^n个子集,将0~(2^n-1)转化为二进制,对应于相应的数字是否输出。
2.全排列问题
#include<iostream> using namespace std; void permute(int pos,int num,int data[],int tmp[],bool label[]){ if(pos==num){ for(int i=0;i<num;i++){ cout<<tmp[i]; } cout<<endl; //return;//也可以不去掉这一句,但现在还不知道为什么?? } for(int j=0;j<num;j++){//这个循环其实跟上面的代码类似,只不过上面每层只有两个选择(选或不选),现在每层有num个选择 if(!label[j]){ tmp[pos]=data[j]; label[j]=true; permute(pos+1,num,data,tmp,label); label[j]=false; } } } int main(){ int data[4]={1,2,3,4}; int tmp[4]; int num=4; bool label[4]={false}; permute(0,num,data,tmp,label); system("pause"); return 0; }
网上有更好的程序
#include<iostream> using namespace std; int a[4] = {1,2,3,4}; const int N = 4; void print(){ for(int i = 0; i < N; i++) cout << a[i] << " "; cout << endl; } void swap(int *a,int i,int j){ int temp; temp = a[i]; a[i] = a[j]; a[j] = temp; } void backtrack(int i){ if(i >= N){ print(); } for(int j = i; j < N; j++){ swap(a,i,j); backtrack(i+1); swap(a,i,j); } } int main(){ backtrack(0); return 0; }
求解思想: 排列中每一个数字,都有一次当最前单一缀的机会, 例如: 排列数组 a[] 1.当a[]中只有1个数字的时候,(a[1]={a1})则只有1个数字做单一前缀和后缀,则只有一种(1!=1)可能 a1,直接输出 2.当a[]中只有2个数字的时候,(a[2]={a1,a2}) ,则a1,a2分别有一次机会做单一前缀的机会,(2!=2) ,{a1 ,a2} {a2,a1} 3,当a[]中有3个数字时候,(a[3]={a1,a2,a3}) ,3个数字分别有一次做前缀的机会,则固定一个数字做前缀有3中情况(a1..... a2........ a3........) ,后面2个数字如同情况2. 故有3!=6中排列。 4.当a[]的数字数目大于2情况都如同情况3 。[code]#include<iostream> using namespace std; void swap(int &a,int &b) { int temp ; temp=a; a=b; b=temp; } void show(int a[],int n) //显示全部数组 { for(int i=0;i<n;i++ ) { cout<<a[i]<<" "; } cout<<endl; } void prim(int a[],int k, int n) //n是这个a[]中有多少个元素 ,k是a[]需要全排列的的坐下标 { if(k==n-1)//不是只有一个元素 而是全排列到最后一个数字时 终止递归的条件 { show(a,n); } else { for(int i=k;i<n;i++) //从k开始时保证交换和递归次数 { swap(a[i],a[k]); //第一次 自己和自己交换即自己是最前单一前缀 交换单一前缀和后缀中的每一个元素 ,让每一个元素都可以做前缀 prim(a,k+1,n); swap(a[i],a[k]); //回溯之后 仍然恢复交换以前的顺序 } } } int main(int argc, char* argv[]) { int a[3]={1,2,3}; prim(a,0,3); system("PAUSE"); return 0; }
[/code]
这两个问题很有代表性,事实上有许多问题都是从这两个问题演变而来的。第一个问题,它穷举了所有问题的子集,这是所有第一种类型的基础,第二个问题,它给出了穷举所有排列的方法,这是所有的第二种类型的问题的基础。理解这两个问题,是回溯算法的基础.
3.子集和问题
整数集合data[]和一个整数sum,求集合data[]的所有子集sub,使得sub的元素之和为sum。这就是子集问题演变过来的
#include<iostream> using namespace std; void SumofSub(int start,int data[],int num,int sum,int label[]){ if(start>=num){ if(sum==0){// for(int i=0;i<num;i++){ if(label[i]) cout<<data[i]<<" "; } cout<<endl; } return;//不能少 } if(data[start]<=sum){ label[start]=true; SumofSub(start+1,data,num,sum-data[start],label); } label[start]=false; SumofSub(start+1,data,num,sum,label); } int main(){ int data[]={1,2,3,4,5}; int num=5; int sum=7; int label[5]; SumofSub(0,data,num,sum,label); system("pause"); return 0; }
4.八皇后问题
八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上不能在同行,同列,以及斜线上。
这里的斜线不是仅只对角线,所以最好用斜率来衡量,即任意两皇后之间的斜率不等于±1
为了满足要求每行必须是要放一个皇后的,用回溯法找到所有合适的位置
1)先从首位开始检查,如果不能放置,接着检查该行第二个位置,依次检查下去,直到在该行找到一个可以放置一个皇后的地方,然后保存当前状态,转到下一行重复上述方法的检索。
(2)如果检查了该行所有的位置均不能放置一个皇后,说明上一行皇后放置的位置无法让所有的皇后找到自己合适的位置,因此就要回溯到上一行,重新检查该皇后位置后面的位置
(1)递归
错误的一次是
#include<iostream> #include<cstdlib> using namespace std; //注意abs是定义在cstdlib库中的 bool check(int i,int column[]){ for(int j=0;j<i;j++){ if(column[j]==column[i]||abs(column[i]-column[j])==(i-j)){//斜率等于±1 return false; } } return true; } //start表示搜索的行,column表示该行的所放的列 void eightQueen(int start,int column[],int &sum){ if(start>=8){ for(int j=0;j<8;j++){ cout<<column[j]<<" "; } cout<<endl; sum++; return; } for(int j=0;j<8;j++){ //column[start]=j; if(check(start,column)){ column[start]=j; eightQueen(start+1,column,sum); } }//end for } int main(){ int column[8]; int sum=0; eightQueen(0,column,sum); cout<<sum<<endl; system("pause"); return 0; }
错误的关键就是column[start]=j写在了if内部,导致column[]没有更新,所以check()会无法判别
正切的是该句在外面
结果有92种。
相关文章推荐
- 八皇后问题(DFS加回溯)输出排列的所有情况
- 回溯法解决八皇后和0,1背包问题和排列问题
- 回溯题目/排列、组合、子集问题
- 圆排列问题-回溯法
- 回溯算法---八皇后问题
- 回溯法-子集和数问题
- 子集与子集和问题(Subset sum)的递归回溯解
- 八皇后问题回溯算法演示系统
- 回溯算法之 幂集问题(组合问题)
- 【算法复习二】八皇后问题 ---- 回溯
- 回溯法解八皇后、幕集输出、迷宫问题
- 【算法总结-排列组合与子集问题】排列组合与子集问题
- 全排列问题(回溯求解)
- 回溯法:输出n的全排列,解哈密顿问题,马的遍历问题,解八后问题
- 回溯法-八皇后问题之C实现
- 回溯法求排列问题
- 八皇后问题 回溯递归 C语言版
- 八皇后问题回溯算法演示系统
- 回溯法-------子集和数问题
- 字符串数组的全排列到八皇后问题详解