全排列的生成算法(六)——回溯法
回溯法通常是构造一颗生成树。以3个元素为例;树的节点数据可取值是1、2、3。如果某个节点为0,则表示尚未取值。
初始状态是(0,0,0),第1个元素值可以分别挑选1,2,3,因此扩展出3个子结点。用相同方法找出这些结点的第2个元素的可能值,如此反复进行,一旦出现新结点的3个数据全非零,那就找到了一种全排列方案。当尝试了所有可能方案,即获得了问题的解答。
回溯法有非递归和递归两种形式。递归形式的程序代码比较简洁,但是递归会带来运行时间和内存的额外消耗,效率较低。非递归形式的回溯法效率要高一些,但程序代码比较复杂。
//回溯(非递归)
//输入:排列元素个数n
//输出:n个元素的全体排列
#include <iostream>
#include <iomanip>
using namespace std;
void output(int *,int);
void backdate(int *,int);
int total;
int main(void)
{
freopen(“in.txt”,”r”,stdin);
//
freopen(“out.txt”,”w”,stdout);
int n,*p;
while(cin>>n)
{
p=new int[n];
fill_n(p,n,0);
total=0;
backdate(p,n);
}
return 0;
}
void backdate(int *p,int n)
{
int i=0,j=1; //i是排列元素的序号,j是排列元素所取得值
while(1)
{
while(j<=n) //如果p[i]所取得值在可取值范围,继续
{
for(int k=0;k<i;k++) //p[i]所取得值是否与前面元素的值重复
if(p[k]==j)
break;
if(k==i) //不重复
{
p[i++]=j; //p[i]取值j
j=1; //继续为下一个排列元素取值
break;
}
j++; //换一个
}
if(i==n) //如果排列的那个元素都已选定
{
output(p,n); //输出一个排列
j=n+1; //调整最后一个元素的值,准备回溯,找下一个排列
}
if(j>n) //当j超出可取值范围
{
j=p[–i]+1; //回溯
if(i<0) //不能回溯
break; //结束
}
}
}
void output(int *p,int n)
{
cout<<setw(5)<<++total<<“: “;
for(int i=0;i<n;i++)
cout<<setw(2)<<p[i];
cout<<endl;
}
//回溯算法(递归)
//输入:排列元素个数n
//输出:n个元素的全体排列
#include<iostream>
#include <iomanip>
using namespace std;
void output(int *,int);
void backdate(int *,int,int);
int total;
int main(void)
{
freopen(“in.txt”,”r”,stdin);
//
freopen(“out.txt”,”w”,stdout);
int n,*p;
while(cin>>n)
{
p=new int[n];
fill_n(p,n,0);
total=0;
backdate(p,n,0);
}
return 0;
}
void backdate(int *p,int n,int k)
{
if(k==n) //如果n个排列元素的值都已选定
output(p,n); //输出一个排列
else //否则,继续选择排列元素的值
for(int i=1;i<=n;i++) //i是排列元素可取的值
{
for(int j=0;j<k;j++) //i是否与已选的元素重复
if(p[j]==i)
break;
if(j==k) //不重复
{
p[k]=i; //p[i]取值i
backdate(p,n,k+1); //继续
}
}
}
void output(int *p,int n)
{
cout<<setw(5)<<++total<<“: “;
for(int i=0;i<n;i++)
cout<<setw(2)<<p[i];
cout<<endl;
}