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

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 。

#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;
}






这两个问题很有代表性,事实上有许多问题都是从这两个问题演变而来的。第一个问题,它穷举了所有问题的子集,这是所有第一种类型的基础,第二个问题,它给出了穷举所有排列的方法,这是所有的第二种类型的问题的基础。理解这两个问题,是回溯算法的基础. 

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种。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值