全排列问题【递归】
SDUT题目链接
Description
从n个不同元素任取m(m<=n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列,当m=n时所有的排列情况叫全排列。现输入n个递增的数,请你输出这n个数的全排列。全排列输出顺序如样例所示。
Input
第一行先输入一个整数n(1<=n<=10)。
接下来是一行输入n个由空格分开的互不相同的整数num (1 <= num <= 90000)。
Output
对于每组数据,每一种排列占一行,各元素间用逗号隔开。
Sample
Input
3
1 2 3
Output
1,2,3
1,3,2
2,1,3
2,3,1
3,1,2
3,2,1
题解思路:写这个题主要原因是觉得它有一些苛判条件比较恶心,但是其中的递归调用我觉得还是很有趣的。
思路的话还是举例子,比如对这三个数全排列:
1 2 3
我们可以根据题目给的Output看出,当1在第一位的时候,2和3进行了全排列,2在第一位的时候1和3进行了全排列。
总结规律可以得到,全排列就是把当前待全排列的元素的第一个元素依次与后边的元素进行交换,比如把1 2 3 的 3 提前变成
3 1 2
这样我们就只需要把1和2进行全排列即可。
思路比较简单,下面附上注意事项和代码。
注意:
每次递归调用perm之前我们都要保证这个序列是和字典序一样的递增的序列(使用Move函数),然后遍历这(l+1,r)的子序列,在遍历之前,我们所换走的元素之前的元素是已经排序好的,而换走的元素应该是子区间的第一个元素,比如
1 2 3 4 5
当我递归进行到,把4提前
4 2 3 1 5
这时我们就拿出2 3 1
这个区间,让他变成1 2 3
,这样我们再把
1 2 3 5
这个递增的子区间去实现下一层的递归。
最后就是去回溯还原。就是递归完再move还原递增排序之前的序列和swap还原一开始那个交换次序。如果不保证每次perm的元素都是字典序的话题目会判WA,相当恶心。
代码如下:
#include <iostream>
#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
const int N = 1e5 + 10;
const int M = 111;
using namespace std;
int a[11];
///右移元素,保证递增
void move_r(int l,int r){
if(l>=r) return ;
int t=a[r];
for(int i=r;i>l;i--) a[i]=a[i-1];
a[l]=t;
}
///左移元素,还原序列
void move_l(int l,int r){
if(l>=r) return ;
int t=a[l];
for(int i=l;i<r;i++) a[i]=a[i+1];
a[r]=t;
}
///递归全排列
void perm(int l,int r){
if(l==r){
for(int i=1;i<=r;i++){
if(i==r) cout<<a[i]<<endl;
else cout<<a[i]<<",";
}
}
else{
for(int i=l;i<=r;i++){
swap(a[l],a[i]);//一个元素拿到前面,对后面的元素进行全排列
move_r(l+1,i);//保持子序列递增
perm(l+1,r);//对子序列递归
move_l(l+1,i);//还原递增之前的子序列
swap(a[l],a[i]);//还原交换之前的序列
}
}
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
perm(1,n);
return 0;
}
当然,这个题用dfs实现起来还是很简单的,也不需要考虑字典序的问题:
#include<bits/stdc++.h>
using namespace std;
const int N=100005;
bool vis[10];
int path[N];
int n;
void dfs(int x)
{
if(x==n+1)
{ for(int i=1;i<=n;i++)
cout<<path[i]<<" ";
puts("");
return ;
}
for(int i=1;i<=n;i++)
{
if(!vis[i])
{ vis[i]=true;
path[x]=i;
dfs(x+1);
vis[i]=false;
}
}
}
int main()
{
scanf("%d",&n);
dfs(1);
}
新手起步,多多包涵。