7.2 枚举排列

有没有想过如何打印所有排列呢?输入整数n,按字典序从小到大的顺序输出前n个数的所有排列。两个序列的字典序大小关系等价于从头开始第一个不相同位置处的大小关系
eg:(1,3,2)<(2,1,3),字典序最小的排列是(1,2,3,4,...,n),最大的排列是(n,n-1,n-2,...,1)

7.2.1 生成1~n的排列

点击查看笔者代码
#include<iostream>
#include<algorithm>
using namespace std;

int n, v[100], ans[100];

void dfs(int p, int val[100]) {
  if(p==n) {
    for(int i = 0; i < n; i++) cout << ans[i] << " ";
    cout << endl;
  	return;
  }
  for(int i = 0; i < n; i++) {
  	if(val[i]) {
  	  ans[p] = val[i];
  	  val[i] = 0;
	  dfs(p+1, val);
	  val[i] = i+1;	
    }
  }	
}

int main() {
  cin >> n;
  for(int i = 0; i < n; i++) v[i] = i+1;
  dfs(0, v);
  return 0;
}
我们尝试使用递归的思想解决,先输出所有以1开头的排列(这一步是递归调用),然后尝试输出以2开头的排列(又是递归调用),接着是以3开头的排列......最后才是以n开头的排列 以1开头的排列的特点是,开头是1,后面是2-9的排列。根据字典序的定义,这些2-9的排列也必须按照字典序排列。换句话说需要按照字典序输出2-9的排列,开始出现递归的意思了 需要注意的是,需要在每个排列的最前面加上'1',这样一来,所设计的递归函数需要注意以下参数 1.已经确定的“前缀”序列,已便输出 2.需要进行全排列的元素集合,一边依次选做第一个元素 这样可以得到一个伪代码 void print_permutation(序列A, 序列S) { if(S为空) 输出序列A; else 按照从小大的顺序依次考虑S的每个元素V { print_permutation(在A的末尾添加v后得到新的序列, S-{v}); } }

下面考虑程序实现,很明显序列A可以用数组表示,注意这边作者的构思最巧妙的地方在于S根本没必要出现,因为用一个参数记录当前所在位置,那么数组A后面的元素都是待排列的

代码实现如下

点击查看代码
void print_permutation(int n, int* A, int cur) {
  if(cur == n) {
    for(int i = 0; i < n; i++) printf("%d ", A[i]);
	printf("\n"); 
  }
  else for(int i = 1; i <= n; i++) { //尝试在A[cur]中填各种整数
    int ok = 1;
	for(int 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); //递归调用 
	}
  
  }
}

循环变量i是当前考察的A[cur]。这边通过标志变量ok来实现对于i是否用过的检测

7.2.2 生成可重集的排列

因为之前的代码作者是认为每个元素都不相同来进行全排列的,此时里面的元素出现了重数,所以我们也要对应的多出一维的描述量来记录i元素出现的次数,和原先集合中i元素出现的次数,只要没有超出,就可以继续递归调用,但是这样子同样是会存在问题的,因为这样子相同的元素此时是当作不同的元素来看待的,但是理论上1(1)1(2)1(3)和1(2)1(1)1(3)这两个组合应该是一样的,但是不同的1出现的次数此时认为是不同的,那么解决办法就是因为所需要排列的组合在之前就进行过排序,那么从第一元素之后,只要和前面的元素相同那么cur这个地方就不能再填入该元素了,也就是变相的实现了,相同元素只能填入一次,同时也非常符合全排列的实现,也就是对于cur这个位置,是当前可以填入的元素的集合,各有且只有一个元素类似于set

点击查看代码
#include<iostream>
using namespace std;

void print_permutation(int n, int* P, int* A, int cur) {
  if(cur ==n) {
  	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]) continue;//这边主要是排序过的因此,重复的元素是集合成一起的,所以可以使用,同时利用短路机制实现的 
  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[i] == P[j]) c2++;
  if(c1 < c2) {
  	A[cur] = P[i];
  	print_permutation(n, P, A, cur+1);
  }
} 
/*
 else for(int i = 1; i <= n; i++) {
  	int ok = 1;
  	for(int j = 0; j < cur; j++)
  	  if(A[j] == i) ok = 0;
  	if(ok) {
  	  A[cur] = i;
  	  printf_permuataion(n, A, cur+1);
	}
  } 
*/
  
}

int main() {
  int n1[10], n2[10];
  for(int i = 0; i < 10; i++) {
  	n1[i] = 1;
  	n2[i] = i+1;
  }
  print_permutation(3, n1, n2, 0);
  return 0;
}
posted @   banyanrong  阅读(46)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示