一、全排列的实现
方案一:用子集树实现
// 注意: i不能作为全局变量
#include <iostream>
using namespace std;
int k,n;
int x[1000];
int vis[1000];
void show()
{
for(int i=1;i<=n;i++)
cout<<x[i];
cout<<endl;
}
void dfs(int k)
{
if(k>n)
{
show();
return;
}
else
{
for(int i=1;i<=n;i++)
{
if(vis[i]==0)
{
x[k]=i;
vis[i]=1;
dfs(k+1);
vis[i]=0;
}
}
}
}
int main()
{
cin>>n;
dfs(1);
}
方案二:用排列树实现
// 注意: x数组必须先赋值
// i不能作为全局变量
#include <iostream>
using namespace std;
int k,n;
int x[1000];
void show()
{
for(int i=1;i<=n;i++)
cout<<x[i];
cout<<endl;
}
void dfs(int k)
{
if(k>n)
{
show();
return;
}
else
{
for(int i=k;i<=n;i++)
{
swap(x[i],x[k]);
// 如果有剪枝函数的话,写在这里。 只把 “dfs(k+1);” 语句写在if语句里面
dfs(k+1);
swap(x[i],x[k]);
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
x[i]=i;
dfs(1);
}
全排列的生成会有两个问题,分别是重复问题和镜像问题。以排列树为例,引出去重复和去镜像策略。
二、去重复(check函数)
方案一
回溯前先对原始序列进行排序。如果当前的数与它前一个相等,则不允许当前的数执行swap动作。
核心在于比较的是当前位和前一位是否相等,这样就能保证对于重复的元素,有且仅有第一个出现的进行交换。保证重复元素只使用一次。
#include <algorithm>
#include <iostream>
using namespace std;
int k,n;
int x[1000];
void show()
{
for(int i=1;i<=n;i++)
cout<<x[i];
cout<<endl;
}
void dfs(int k)
{
if(k>n)
{
show();
return;
}
else
{
for(int i=k;i<=n;i++)
{
if(i!=k && x[i]==x[i-1])
continue;
swap(x[i],x[k]);
dfs(k+1);
swap(x[i],x[k]);
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>x[i];
sort(x,x+n);
dfs(1);
}
方案二
若想选第i个数交换到k位置上,需要判断 (i,n] 这一区间中是否有与第i个数相等的元素。若有,则不允许第i个数交换过来。跳过这个i就可以,毕竟后面还有“i”。这样就能保证对于重复的元素,有且仅有最后一个出现的进行交换。保证重复元素只使用一次。
#include <algorithm>
#include <iostream>
using namespace std;
int k,n;
int x[1000];
void show()
{
for(int i=1;i<=n;i++)
cout<<x[i];
cout<<endl;
}
// 若想选第 i 个数交换到 k 位置上,需要判断 (i,n] 这一区间中是否有与第 i 个数相等的元素。若有,则不允许第 i 个数交换过来。跳过这个 i 就可以,毕竟后面还有 “i”。这样就能保证对于重复的元素,有且仅有最后一个出现的进行交换。保证重复元素只使用一次。
int check(int j)
{
for(int i=j+1;i<=n;i++)
{
if(x[i]==x[j])
return 0;
}
return 1;
}
void dfs(int k)
{
if(k>n)
{
show();
return;
}
else
{
for(int i=k;i<=n;i++)
{
if(!check(i))
continue;
swap(x[i],x[k]);
dfs(k+1);
swap(x[i],x[k]);
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>x[i];
dfs(1);
}
方案三
若想选第i个圆到k位置上,需要判断[k,i)这一区间中是否有圆与第i个圆等大。如有,则不允许第i个圆换过来
#include <iostream>
using namespace std;
int k,n;
int x[1000];
void show()
{
for(int i=1;i<=n;i++)
cout<<x[i];
cout<<endl;
}
// 若想选第 i 个圆到 k 位置上,需要判断 [k,i) 这一区间中是否有圆与第 i 个圆等大。如有,则不允许第 i 个圆换过来
int check(int start,int end)
{
for(int i=start ; i<end ; i++)
{
if(x[i]==x[end])
return 0;
}
return 1;
}
void dfs(int k)
{
if(k>n)
{
show();
return;
}
else
{
for(int i=k;i<=n;i++)
{
if(!check(k,i))
continue;
swap(x[i],x[k]);
dfs(k+1);
swap(x[i],x[k]);
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>x[i];
dfs(1);
}
三、去镜像
思路:在每个序列生成的过程中,时刻保证第一位小于最后一位,就不会出现镜像,可以少考虑一半的数。
办法:先为第一位从n个数中挑选一个数,然后从剩下的n-1个数里为第n位选一个比第一位大的数,然后正常的生成2~n-1这一区间的全排列。
生成完这n-2个数之后,就得到了一个解。改变第n个数,继续正常的生成2~n-1这一区间的全排列。直到第n位已经没有别的可用的数,去更换第1位数,再重复执行上述所有过程...
注意:如果去镜像前没有去重复,那么去镜像操作不会有减半效果。
#include <iostream>
using namespace std;
int k,n,flag;
int x[1000];
void show()
{
for(int i=1;i<=n;i++)
cout<<x[i];
cout<<endl;
}
void dfs(int k)
{
if(k>n-1)
{
show();
return;
}
else
{
for(int i=k;i<=n-flag;i++)
{
if(k==1 || flag) // flag的作用是保证正常的进行[2,n-1]区间的全排列的生成
{
swap(x[i],x[k]);
dfs(k+1);
swap(x[i],x[k]);
}
else
{
if (x[i] > x[1])
{
swap(x[i],x[n]);
flag=1;
dfs(2);
flag=0;
swap(x[i],x[n]);
}
}
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>x[i];
dfs(1);
}