康拓排列的自我总结--以及全排列的递归非递归算法

写了几个关于全排列的东西,然后就接触到了康拓排列。之前对于全排列的非递归算法耿耿于怀,一只不能找到好的方式。现在好了,有了康拓,什么都解决了。

递归求全排列

我们先来看一个简单的例子,就是如何递归的求全排列。

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}。

康拓排列有一个前提是里面的元素不能有重复的!

求下一个排列

这是一个经典问题,前面已经减了什么事康拓排列了,那么给你一个排列,让你求它的下一个排列是什么,这个算法应该怎么写呢?比如上述例子,{2,1,3}的下一个排列是{2,3,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},它有在哪一个呢?
这个的算法的思想如下:
先了解什么是康拓展开
形如下式的炸开叫做康拓展开,
X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0!
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}中第几个大的数。
于是我们建立3个桶,我们知道3个桶里面已经放好数字了,分别是3,2,1.现在我们想知道这个数字是第几大的。怎么办呢,我们看第一个桶,第一个桶中的数字是3,但是第一个桶我们知道如果你来放的话,是可以放1,2,3三个数字的,那么这里面比三小的数字有1,和 2连个,于是,由1或者2开头的全排列都比3开头的全排列要小(100多肯定小于300多啊),于是我们知道由1开头的有1*2!(阶乘)个,由2开头的有1*2!(阶乘)个,所以,这里有2*2!个。再看第二个桶,同理,第二个桶被放了2,此时第一个桶已经放好3了,所以第二个桶你来放的话,只能放2和1。我们知道,32*肯定比31*要大,而满足这个条件的,只有1*1!(阶乘)种。最后第三个桶,已经没有比它小的了,所以只能乘以0了。于是就是2*2!+1*1!+0*0!=5.所以这个排列是第6大的(别忘记加1)
再举个例子:1324是{1,2,3,4}排列数中第几个大的数:第一位是1小于1的数没有,是0个,0*3!,第二位是3小于3的数有1和2,但1已经在第一位了,所以只有一个数2,1*2! 。第三位是2小于2的数是1,但1在第一位,所以有0个数,0*1!,所以比1324小的排列有0*3!+1*2!+0*1!=2个,1324是第三个大数。
代码非常简单,如下
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个排列。

这个题目有两种做法,一种是执行k-1次nextPermutation,另外一种就是使用康托排列了。重点讲康托排列。
如何找出第16个(按字典序的){1,2,3,4,5}的全排列?

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;
	}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章