寫了幾個關於全排列的東西,然後就接觸到了康拓排列。之前對於全排列的非遞歸算法耿耿於懷,一隻不能找到好的方式。現在好了,有了康拓,什麼都解決了。
遞歸求全排列
我們先來看一個簡單的例子,就是如何遞歸的求全排列。
private static void recursionPermutation(String[] s, int k) {
// TODO Auto-generated method stub
if (k == s.length - 1) {
for (int i = 0; i < s.length; i++) {
System.out.print(s[i]+" ");
}
System.out.println();
}
for(int i=k;i<s.length;i++){
swap(s,i,k);
recursionPermutation(s, k+1);
swap(s,k,i);
}
}
上述代碼對String數組s進行全排列,當然你也可以對int數組全排列, 換一下就好了,無所謂的。採用的是遞歸的方式,k相當於一個標杆。對這個標杆和其他元素交換,然後遞歸的調用,然後恢復原來的狀態。在把標杆k和其他的交換。遞歸的結束條件是k等於最後一個了,說明已經前面的都當過標杆了,都交換過了,所以要輸出。
上述就是求全排列的遞歸算法了,那麼這裏引入一個概念,就是康拓排列,什麼是康拓排列呢?一個數組,求它的全排列,然後按照從小到大的順序(這個可以是字典順序啊,或者你定義的其他順序都行)排好,那麼這就是康拓排列。比如{1,2,3},它的康拓排列就是{1,2,3},{1,3,2},{2,1,3},{2,3,1},{3,1,2},{3,2,1}。
康拓排列有一個前提是裏面的元素不能有重複的!
求下一個排列
public class NextPermutation {
//分析,從右到左,找到升序排列的隊列A,然後再往左一個,這個定義爲PartitionNumber
//然後再從右到左,找到比PartitionNumber大的第一個數字,定位ChangeNumber
//交換這兩個數字
//逆序交換後的隊列A
public static void main(String[] args){
int[] nums ={1,2,4,3};
int[] next = NextPermutation(nums);
System.out.println(Arrays.toString(next));
}
public static int[] NextPermutation(int[] nums) {
// TODO Auto-generated method stub
//找到從右往左升序的隊列,並找到PationNumber;
int pivot = nums.length-1;
while(pivot>0&&nums[pivot]<nums[pivot-1]){
pivot--;
}
int partitionNumberSite = pivot-1;
int partitionNumber = nums[partitionNumberSite];
//找到比partitionNumber大的第一個數字,即changeNumber的位置;
int i;
for(i=nums.length-1;i>=pivot;i--){
if(nums[i]>partitionNumber){
break;
}
}
//交換兩個元素
swap(nums,i,partitionNumberSite);
//逆序排序
for(int j=pivot,k=nums.length-1;j<k;j++,k--){
swap(nums, j, k);
}
return nums;
}
private static void swap(int[] nums, int i, int j) {
// TODO Auto-generated method stub
int temp = nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
}
有了求下一個排列,那麼你的康拓全排列是不是就很簡答就能求出來了呢?只需要連續調用n!次nextPermutation方法,就可以非遞歸的求得所有的全排列。判斷一個排列的位置
康拓排列有一個典型的應用就是判斷一個排列的位置,給你一個排列){1,2,3},那麼這個排列肯定是第一個啊。那麼對於四個數字的呢?5個數字的{1,3,5,2,4},它有在哪一個呢?ai爲整數,並且0<=ai<i(1<=i<=n),其中ai爲小於上一個數字的個數。
適用範圍:沒有重複元素的全排列
{1,2,3,4,...,n}的排列總共有n!種,將它們從小到大排序,怎樣知道其中一種排列是有序序列中的第幾個?
如 {1,2,3} 按從小到大排列一共6個:123 132 213 231 312 321。想知道321是{1,2,3}中第幾個大的數。
代碼非常簡單,如下
public static int cantor(int[] nums){
int result =0;
int length = nums.length;
for(int i=0;i<length;i++){
int temp=0;
for(int j=i+1;j<length;j++){
if(nums[i]>nums[j]){
++temp;
}
}
result+=temp*factorial(length-i-1);
}
return result+1;
}
給你一個數組,求第k個排列。
1. 首先用16-1得到15
2. 用15去除4! 得到0餘15
3. 用15去除3! 得到2餘3
4. 用3去除2! 得到1餘1
5. 用1去除1! 得到1餘0
有0個數比它小的數是1,所以第一位是1
有2個數比它小的數是3,但1已經在之前出現過了所以是4
有1個數比它小的數是2,但1已經在之前出現過了所以是3
有1個數比它小的數是2,但1,3,4都出現過了所以是5
最後一個數只能是2
所以排列爲1 4 3 5 2
private static void getPermutationByNext(int i, int j) {
// TODO Auto-generated method stub
//初始化數組的值爲1,2,3,......i;
int[] nums = new int[i];
for(int a=0;a<nums.length;a++){
nums[a]=a+1;
}
//因爲第1個是nums,所以只需要再求j-1次即可
for(int a=1;a<j;a++){
nums = NextPermutation.NextPermutation(nums);
}
System.out.println(Arrays.toString(nums));
}
//給定一個數字n,表示從1--n的數組,然後找第k個排列
public static int[] reverseCantor(int n,int k){
int[] result = new int[n];
//用list,當這個元素添加到result數組時,刪除這個元素
ArrayList<Integer> list = new ArrayList<>();
for(int i=0;i<n;i++){
list.add(i+1);
}
//求一開始的階乘(n-1)!
int base=factorial(n-1);
//別忘記減一
int cantor = k-1;
//操作放在for裏面
for(int i=n-1;i>0;cantor%=base,base/=i,i--){
//求得結果,這個結果代表着第a小的值;
int a=cantor/base;
result[n-i-1]=list.get(a);
list.remove(a);
}
//別忘記最後一個元素
result[n-1]=list.get(0);
return result;
}