7.2.1 生成1~n的全排列

#include<stdio.h>
int A[100];

// 输出1~n的全排列
void print_permutation(int n, int* A, int cur) {
int i, j;
if(cur == n) { // 递归边界
for(i = 0; i < n; i++) printf("%d ", A[i]);
printf("\n");
} else for(i = 1; i <= n; i++) { // 尝试在A[cur]中填各种整数i
int ok = 1;
for(j = 0; j < cur; j++)
if(A[j] == i) ok = 0; // 如果i已经在A[0]~A[cur-1]出现过,则不能再选
if(ok) {
A[cur] = i;
print_permutation(n, A, cur+1); // 递归调用
}
}
}

int main() {
print_permutation(4, A, 0);
return 0;
}


 

生成0至 n-1的排列:


bool used[MAX_N]; 
int perm[MAX_N];
// 生成{0,1,2,3,4,...,n-1}的n!种排列
void permutation1(int pos, int n) {
if (pos == n) {
/*
* 这里编写需要对perm进行的操作
*/
return ;
}
// 针对perm的第pos个位置,究竟使用0~n-1中的哪一个进行循环
for (int i = 0; i < n; i++) {
if (!used[i]) {
perm[pos] = i;
// i已经被使用了,所以把标志设置为true
used[i] = true;
permutation1(pos + 1, n);
// 返回之后把标志复位
used[i] = false;
}
}
return ;
}


 

7.2.2 生成可重集的排列

思路:

1、统计已经输出的某个数的次数,如果少于这个数总共出现的次数,就可以选择这个数

2、由于P[i]数据相同,所以可能多次递归相同数据,所以需要把P[i]排序,并且在递归前判断是否和前面的数据P[i-1]是否相同。


#include<cstdio> 
#include<cstring>
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;

const int N=20;
int P[N];
int A[N];

void perm(int n, int *A, int cur)
{
if(n==cur)
{
for(int i=0;i<n;i++)
printf("%d ", A[i]);
printf("\n");
}
else for(int i=0;i<n;i++)
{
int ok=1;
for(int j=0;j<cur;j++)
{
if(A[j]==P[i])
{
ok=0;
break;
}
}
if(ok)
{
A[cur]=P[i];
perm(n, A, cur+1);
}
}
}

//生成可重集的排列(错误的)
void perm2(int n, int *A, int cur)
{
if(n==cur)
{
for(int i=0;i<n;i++)
printf("%d ", A[i]);
printf("\n");
}
else for(int i=0;i<n;i++)
{
int c1=0, c2=0;
for(int j=0;j<cur;j++) if(A[j]==P[i]) c1++;
for(int j=0;j<n;j++) if(P[j]==P[i]) c2++;
if(c1<c2)
{
//printf("%d %d\n", c1, c2);
A[cur]=P[i];
perm2(n, A, cur+1);
}
}
}

//生成可重集的排列(修正后)
// P 需要排序过
// 输出数组P中元素的全排列。数组P中可能有重复元素
void perm3(int n, int *A, int cur)
{
if(n==cur)
{
for(int i=0;i<n;i++)
printf("%d ", A[i]);
printf("\n");
}
else for(int i=0;i<n;i++)
if(!i || P[i]!=P[i-1])
{
int c1=0, c2=0;
for(int j=0;j<cur;j++) if(A[j]==P[i]) c1++;
for(int j=0;j<n;j++) if(P[j]==P[i]) c2++;
if(c1<c2)
{
//printf("%d %d\n", c1, c2);
A[cur]=P[i];
perm3(n, A, cur+1);
}
}
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
cin>>P[i];

printf("perm\n");
perm(n, A, 0);

printf("perm2\n");
perm2(n, A, 0);

printf("perm3\n");
perm3(n, A, 0);

return 0;
}


 

7.2.3 解答树

一次生成一个解答元素

每个叶子对应一个排列,共有n!个叶子。 共有e*n!个节点。

最后一层叶子n!个,倒数第二层也是n!个,所以上面各层总共加起来也不到n!个。

就是排列树啦!!

7.2 枚举排列_i++

 

7.2 枚举排列_数据_02

3个元素情况如上,叶子有3!=6, 倒数第二层也是3!=6个。

7.2.4 下一个排列

用stl的next_permutation

注意,return false时有回到最小序列

 


#include<cstdio>
#include<algorithm>
using namespace std;

/*
函数实现原理如下:
在当前序列中,从尾端往前寻找两个相邻元素,前一个记为*i,后一个记为*ii,并且满足*i < *ii。然后再从尾端寻找另一个元素*j,如果满足*i < *j,即将第i个元素与第j个元素对调,并将第ii个元素之后(包括ii)的所有元素颠倒排序,即求出下一个序列了。
*/
bool next_perm(int *first, int *last)
{
//空区间
if(first==last) return false;
int *i=first;
//只有一个
if(++i==last) return false;
i=last; --i;
while(1) {
int *ii=i;
--i;
//锁定两个相邻元素
if(*i<*ii) {//如果前一个比后一个小
int *j=last;
while(!(*i<*--j));//从尾向前找,直到找到一个比*i大的元素
printf("swap %d %d\n", *i, *j);
iter_swap(i, j);//交换i,j
reverse(ii, last);//将从ii开始之后的元素逆序重排
return true;
}
if(i==first)//到最前面了,没有更大的序列了
{
reverse(first, last);//全部逆序排列,变成最小的序列
return false;
}
}
}

int main() {
int n, p[10];
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%d", &p[i]);
sort(p, p+n); // 排序,得到p的最小排列
do {
for(int i = 0; i < n; i++) printf("%d ", p[i]); // 输出排列p
printf("\n");
//} while(next_permutation(p, p+n)); // 求下一个排列
} while(next_perm(p, p+n)); // 求下一个排列
//最后会输出最小序列
for(int i = 0; i < n; i++) printf("%d ", p[i]); // 输出排列p
return 0;
}