2-9 排列的字典序问题
问题描述
n个元素{1,2,…,n}有n!个不同的排列。将这n!个排列按字典序排列并编号为0,1,…,n!-1。每个排列的编号为其字典序值。例如,当n=3时,6个不同排列的字典序值如下:
字典序值 | 0 | 1 | 2 | 3 | 4 | 5 |
排列 | 123 | 132 | 213 | 231 | 312 | 321 |
给定n,以及n个元素{1,2,…,n}的一个排列,计算出这个排列的字典序值,以及按字典序排列的下一个排列。
全排列
Java: version-1
import java.util.*;
public class Main {
private static int n;
private static String inputStr;
private static char[] strToChar = new char[1000];
private static String tmpPerm;
private static Set<String> permSet = new HashSet<>();
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
while(true){
permSet.clear();
System.out.println("Please input the length of string: ");
n = input.nextInt();
System.out.println("Please input a digit string: ");
inputStr = input.next();
System.out.println("-------------------------");
strToChar = inputStr.toCharArray();
permutate(strToChar, 0, n-1);
int i=0;
int size = permSet.size();
String[] numbers = new String[size];
Iterator<String> iterator = permSet.iterator();
while (iterator.hasNext()){
String value = iterator.next();
numbers[i++] = value;
}
Arrays.sort(numbers);
for(i=0; i<size; i++){
if(numbers[i].equals(inputStr)) {
System.out.println("Permutation at: "+i);
if(i+1 < size)
System.out.println("Next permutation: "+numbers[i+1]);
else
System.out.println("There is no permutation after: "+numbers[i]);
}
}
System.out.println("------------------------------------------");
}
}
private static void permutate(char[] charList, int k, int m){
if(k == m){
tmpPerm = String.valueOf(charList);
permSet.add(tmpPerm);
return;
}
for(int i=k; i<=m; i++){
swap(charList, k, i);
permutate(charList, k+1, m);
swap(charList, k, i);
}
}
private static void swap(char[] charList, int k, int i){
char temp;
temp = charList[k];
charList[k] = charList[i];
charList[i] = temp;
}
}
Input & Output
Please input the length of string:
8
Please input a digit string:
26458173
-------------------------
Permutation at: 8227
Next permutation: 26458317
------------------------------------------
Please input the length of string:
8
Please input a digit string:
87654321
-------------------------
Permutation at: 40319
There is no permutation after: 87654321
------------------------------------------
Please input the length of string:
8
Please input a digit string:
87654312
-------------------------
Permutation at: 40318
Next permutation: 87654321
------------------------------------------
Please input the length of string:
6
Please input a digit string:
231456
-------------------------
Permutation at: 144
Next permutation: 231465
------------------------------------------
Please input the length of string:
数学推导
Java: version-2
import java.util.Scanner;
public class Main{
private static boolean hasNextPerm = true;
public static void main(String[] args){
int n;
Scanner input = new Scanner(System.in);
while(true){
System.out.println("Input the number of digits:");
n = input.nextInt();
int[] numbers = new int[n];
System.out.println("Input digits:");
for(int i=0; i<n; i++){
numbers[i] = input.nextInt();
}
int rank = dictRank(numbers, n);
nextPermutation(numbers, n);
System.out.println("Permutation at: "+rank);
if(hasNextPerm) {
System.out.print("Next Permutation: ");
}
else
System.out.print("There is no permutation after: ");
for (int i = 0; i < n; i++)
System.out.print(numbers[i]);
System.out.println();
System.out.println("-------------------------------------");
}
}
/*
如何得到2 6 4 5 8 1 7 3的下一个排列?
1 从尾部往前找第一个P(i-1) < P(i)的位置
2 6 4 4 5 8 1 <-- 7 <-- 3
最终找到1是第一个变小的数字,记录下1的位置i-1
2 从尾部往前找到第一个大于1的数
2 6 4 4 5 8 1 7 3 <--
最终找到3的位置,记录位置为m
3 交换位置i-1和m的值
2 6 4 4 5 8 3 7 1
4 倒序i位置后的所有数据
2 6 4 4 5 8 3 1 7
*/
//下一个排列
private static void nextPermutation(int[] numbers, int n){
int min=0, max=n-1, i, j;
for(i=n-1; i>0; i--){ //1
if(numbers[i-1] < numbers[i]){
min = i-1;
break;
}
}
if(i == 0)
hasNextPerm = false;
else{
hasNextPerm = true;
for(j=n-1; j>=0; j--){ //2
if(numbers[j] > numbers[min]){
max = j;
break;
}
}
swap(numbers, min, max); //3
reverse(numbers, min+1, n-1); //4
}
}
//倒序
private static void reverse(int[] numbers, int from, int to){
int[] tmp = new int[to+1];
for(int h=from; h<=to; h++)
tmp[h] = numbers[h];
for(int h=from; h<=to; h++)
numbers[h] = tmp[to+from-h];
}
//交换
private static void swap(int[] numbers, int min, int max){
int tmp = numbers[min];
numbers[min] = numbers[max];
numbers[max] = tmp;
}
/*
2 6 4 5 8 1 7 3
total=0;
比2小的数有1个,则 total+=1*7!;
比6小的数有4个,则 total+=4*6!;
比4小的数有2个,则 total+=2*5!;
比5小的数有2个,则 total+=2*4!;
比8小的数有3个,则 total+=3*3!;
比1小的数有0个,则 total+=0*2!;
比7小的数有1个,则 total+=1*1!;
比3小的数没有;
*/
//第一种字典序算法
private static int dictRank(int[] numbers, int n){
int count;
int total = 0;
for(int i=0; i<n-1; i++){
count = 0;
for(int j=i+1; j<n; j++)
if(numbers[j] < numbers[i])
count++;
total += count*factorial(n-i-1);
}
return total;
}
//第二种字典序算法
private static int dictRank2(int[] numbers, int n){
int count;
int sum = 0;
for(int i=0; i<n-1; i++){
count = numbers[i] - 1;
for(int j=0; j<i; j++)
if(numbers[j] < numbers[i])
count--;
sum += count*factorial(n-i-1);
}
return sum;
}
//n的阶乘
private static int factorial(int n){
int total = 1;
for(int i=1; i<=n; i++){
total *= i;
}
return total;
}
}
Input & Output
Input the number of digits:
8
Input digits:
2 6 4 5 8 1 7 3
Permutation at: 8227
Next Permutation: 26458317
-------------------------------------
Input the number of digits:
8
Input digits:
8 7 6 5 4 3 2 1
Permutation at: 40319
There is no permutation after: 87654321
-------------------------------------
Input the number of digits:
8
Input digits:
8 7 6 5 4 3 1 2
Permutation at: 40318
Next Permutation: 87654321
-------------------------------------
Input the number of digits:
6
Input digits:
2 3 1 4 5 6
Permutation at: 144
Next Permutation: 231465
-------------------------------------
Input the number of digits:
Reference
王晓东《计算机算法设计与分析》(第3版)P45