您的位置:首页 > 其它

回溯问题+幂集、排列、子集和问题、八皇后问题

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种。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: