原文地址:排列的算法(一)——字典序算法
1. 排列算法
从n个不同元素中取出n个元素的排列,称为n个不同元素的全排列。
可以证明,n个元素的全排列的总数是n!。
全排列的生成算法就是对于给定的元素集合,用有效的方法将所有可能的全排列无重复无遗漏地列举出来。
n个不同元素的排列都可以与n个自然数1、2、……,n的排列一一对应,所以,这里就以n个数字的排列为例说明排列的生成算法。
全排列的生成算法就是讨论怎样从一个排列生成它一个新的排列的方法。
2. 字典序算法
不同的排列,可以从左到右逐个比较对应的元素的顺序来决定他们的先后顺序。
设有集合{1、2、……n-1、n}
的两个排列[ a0a1 ……aj……an-1]和 [b1b2……bj……bn-1bn],从左端开始,逐个比较它们的对应元素的大小。
如果出现对应数字不相同的第一个位置为 j
,并且aj<bj,则[a0a1……aj……an-1]就排在[b0b1……bj……bn-1]的前面。
例如,对于5个数字的排列
12354
和12345
,两个排列的前三个数字相同,出现不同数字的位置是j=3
,而排列12354
的第四个数字5在排列12345
的第四个数字4之后,所以排列12345
在前,排列12354
在后。根据这样的规定,5个数字的所有的排列中,第一个排列是12345
,最后一个是54321
。这种判断排列顺序的方法称为字典序法。
根据字典序法生成所有全排列的算法如下:
设P是集合{1、2、……n-1、n}
的一个全排列:P = p1p2……pj-1pjpj+1……pn(1 ≤ p1、 p2、……、pn ≤ n-1)
- 第一步:从排列的右端开始,找出第一个比右边数字小的数字的序号
j
,即j = max { i | pi < pi+1 ,i > j } - 第二步:在pj 的右边的数字中,找出所有比pj大的数字中最小的数字pk,即 k = min { i | pi > pj ,i > j}
- 第三步:交换pi,pk
- 第四步:再将排列右端的递减部分 [pj+1pj+2……pn]倒转,就得到了一个新的排列 P’ = p1p2…pj-1pkpnpn-1…pk+1pjpk-1…pj+1
例如
839647521
是数字1~9的一个排列。从它生成下一个排列的步骤如下:
(1) 自右至左找出排列中第一个比右边数字小的数字4
—— 83964
7521
(2) 在该数字后的数字中找出比4大的数中最小的一个5
—— 83964
75
21
(3) 将5与4交换 —— 83965
74
21
(4) 将7421倒转 —— 839651247
所以839647521的下一个排列是839651247
。
为了得到集合{1、2、……n-1、n}的全体全全排列,可以从原始排列12……n开始,按照字典序法逐个生成它们的后继排列。当得到的后继排列是最后一个n……21时就结束。
3. 字典序算法代码:
输入:当前排序顺序int[] p
输出:每轮循环可得到下一个排列顺序,可以用列表存储所有结果
public static void dict(int[] p){
int k = 0,j = 0;
int n = p.length;
// 每轮循环得到当前排列的下一个排列顺序,直至得到最后顺序
while(true){
// 计算下一次排列
// 找出右端第一个小于右侧数字的数字p[j]
for(j = n - 2;j >= 0;j--){
if(p[j] < p[j + 1]){
break;
}
}
if(j < 0){
break;
}
// 找出右端大于p[j]的数字中最小的数字p[k]
for(k = n - 1;k >= 0;k--){
if(p[k] > p[j]){
break;
}
}
// 交换p[j]和p[k]
swap(p,j,k);
k = n - 1;
// 翻转右侧递减部分
while(j < k){
swap(p,++j,k--);
}
// 显示当前排列
for(int i = 0;i < p.length;i++){
System.out.print(p[i]);
}
System.out.println();
}
}