全排列及相关扩展算法(七)——组合数的字典序(另含全章代码整理)

49 篇文章 8 订阅
1.引入概念:要列出一个集合{1,2,3,4}的所有子集是很容易的,我们可以按照二进制数的顺序,0000,0001,0010,0011,0100,0101,0110,0111......来表示我们要取的元素,其中0表示不取,1表示取,这样就获得了一个顺序。而组合也包含在这个顺序当中。我们看从{1,2,3,4}中选取两个元素的所有组合:
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111.

蓝色标记的是我们所取的组合。对应的顺序便是{3,4}{2,4}{2,3}{1,4}{1,3}{1,2}我们可以用字典序对这些组合进行排序:{1,2}{1,3}{1,4}{2,3}{2,4}{3,4}.共6种。按照我的习惯,我们先看看我们平常列举组合数所采取的策略。比如说1,2,3,4,5取3个元素的组合。我们先从小的取:1,2,3.再把最后一个数改变:1,2,4;1,2,5.当到达5之后,含有1,2的所有组合已经被罗列完了。改变2为3,1,3,4;1,3,5;此时我们不能再选择2进行罗列,否则会重复。改变3为4,此时2和3都不能放入,1,4,5.改变4为5时,2,3,4都不能放入,不存在组合了。这时改变1为2。之后不能再选择1罗列。所以:对于一个组合C1C2C3C4...Cr(每个代表一个数,一共r个数,代表一个组合,数从[1,n]中选),由于一个组合自身是不讲顺序的,我们可以对组合进行升序排列,使得C1<C2<C3<...Cr。一开始我们取最小的元素的组合,并按照字典序把Cr慢慢递增1,这显然是在组合不断取字典序的下一个组合,没有夹杂在这这个方法能取到的值中间的其它组合。在所有组合数当Cr到达n之后,Cr无法变大了,就要做其它的事。由于强制了升序,C(r-1) < Cr ,所以C(r - 1) 到达n-1后也得去做其它的事。

2.性质:

C(r - m)到达n - m后就不能再递增上去。由k = r - (r - k)得到
C(k)到达n - (r - k) = n - r + k 后就不能再递增上去。

那么,对于C1C2C3C4...Cr,我们从Cr开始向前找,直到找到有Ci < n - r + i,并执行
Ci = Ci + 1。由于所有的组合都已经强制升序,所以Cj= Ci + (j-i),j = i+1,i+2,...r即它后面的每一项都比前一项大1。由于Ci< n -r + i,Cr = Ci + (r - i) <=n(因为Ci比原来大了1),所以构造出来的新的C1C2C3C4...Cr仍是[1,n]中选r个元素的一个组合,而且显然它在原组合的字典序后。
由此,我们构造出了一个排在原组合的字典序后面的组合。接下来证明它是紧邻着原组合的。

3.证明:假设有一个组合B1B2B3B4...Br的前i-1个元素和C1C2C3C4...Cr一样,且在原组合字典序后,在我们构造的字典序前,那么必有新Ci > Bi > 原Ci,这是不可能的,因为新Ci= 原Ci + 1。同样不可能存在前i个元素一样但在新Ci字典序前的组合,因为强制排序后Cj 到Cr 的每个元素都是最小的。同样不可能存在前k个元素一样(k < i)但在新Ci字典序前的组合。

如果找不到有Ci < n - r + i,说明已经到达最后一个:n -r + 1,n - r + 2,...n.由此我们可以直接得到组合数的相对字典序。下一个组合算法代码:

bool Next_Combination(int A[], int n, int r)
{
	int i;
	sort(A, A + r);
	for (i = r - 1; !(A[i] <n - r + i) && i > 0; i--);
	if (i == 0)
		return false;
	A[i] = A[i] + 1;
	for (int j = i + 1; j<r; j++)
		A[j] = A[i] + j - i;
	return true;
}


4.参考文档

https://wenku.baidu.com/view/8c79a2facc17552706220880.html



PS:全章代码整理:

//https://wenku.baidu.com/view/8c79a2facc17552706220880.html

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<algorithm>
#include<iostream>
#include<windows.h>
#include<math.h>
#include <time.h>  
#include <functional>
using namespace std;

int Count = 0;
void Swap(int &a, int &b)
{
	a == b ? 0 : a ^= b ^= a ^= b;
}


void Print(int A[],int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d%c", A[i], i == n - 1 ? '\n' : ' ');
	}
}
void Permutation(int A[], int m, int n)
{
	if (m == n)
	{
		//Print(A, n);
		Count++;
	}
	else
	{
		for (int i = m; i < n; i++)
		{
			Swap(A[m],A[i]);
			Permutation(A, m + 1, n);
		    Swap(A[m],A[i]);
		}
	}
}



void Reverse(int A[],int a,int b)
{
	while (a < b)
	{
		Swap(A[a], A[b]);
		a++;
		b--;		
	}
	
}
bool next_permutation(int A[], int n)
{
	int i = n - 2;
	while ((A[i + 1] <= A[i])&&i>=0)i--;
	if (i<0)
	{
		Reverse(A,0,n-1);
		return false;
	}
	else
	{
		int j = i+1;
		while ((A[j] > A[i])&&j<n)j++;
		Swap(A[j-1], A[i]);
		Reverse(A ,i+1 , n-1);
		return true;
	}
}
int factorial(int x) { return x > 1 ? x*factorial(x - 1) : 1; }


int* get_permutation_medium(int A[], int n)
{
	int* temp = new int[n-1];

	for (int i = 0; i < n-1; i++)
	{
		temp[i] = 0;
		for (int j = i + 1; j <= n - 1; j++)
		{
			if (A[j] < A[i])
			{
				temp[i]++;
			}
		}
	}
	return temp;
}

int get_permutation_rank(int medium[],int n)
{
	int rank = 0;
	for (int i = 0; i < n - 1;i++)
	{
		rank += medium[i]* factorial(n - 1 - i);
		
	}
	return rank;
}

int* get_permutation(int medium[], int n)
{
	int* temp = new int[n + 1];
	int* permutation = new int[n + 1];
	for (int i = 0; i <= n; i++)
	{
		permutation[i]=temp[i] = i == n ? 1 : medium[i] + 1;
	}

	for (int i = 0; i <= n ; i++)
	{
		for (int j = i - 1; j >= 0; j--)
		{
			if (temp[j] <= temp[i])
			{
				temp[i]++;
				permutation[i]++;
			}
			else
			{
				break;
			}
		}
		sort(temp,temp + i+1, greater<int>());
	}	
	return permutation;
}



int* get_permutation_error(int medium[], int n)
{
	int* temp = new int[n + 1];
	for (int i = 0; i <= n; i++)
	{
		temp[i] = i == n ? 1 : medium[i] + 1;
	}

	for (int i = 0; i <= n; i++)
	{
		for (int j = i - 1; j >= 0; j--)
		{
			if ((medium[j] <= (i == n ? 1 : medium[i])) || (temp[j] <= temp[i]))
			{
				temp[i]++;
			}
		}
	}
	return temp;
}


int* get_permutation_medium_plus(int A[], int n)
{
	int* temp = new int[n];

	for (int i = 0; i < n; i++)
	{
		temp[n-A[i]] = 0;
		for (int j = i + 1; j <= n - 1; j++)
		{
			if (A[j] < A[i])
			{
				temp[n-A[i]]++;
			}
		}
	}

	return temp;
}

int get_permutation_rank_plus(int medium[], int n)
{
	int rank = 0;
	for (int i = 0; i < n-1; i++)
	{
		rank += medium[i];
		rank *= n - i -1;
	}

	return rank;
}

int* get_permutation_plus(int medium[], int n)
{
	int* temp = new int[n];
	memset(temp, 0, n * sizeof(int));
	for (int i = 0; i < n; i++)
	{
		int empty = -1,j=n;//防止末尾已经被占的情况故提前一位
		while (empty < medium[i] && j >= 0)
		{
			j--;
			if (temp[j] <= 0)
			{
				empty++;
			}
		}
		temp[j] = n - i;
	}

	return temp;
}



bool Movable(int A[], bool direct[], int n) //direct参数用于接收每个元素移动方向的数组。
{
	int max = 1;//初始化最大可移动数为1,因为规定1是最小的数,可以自己设定。
	int pos = -1;//初始化最大可移动数的位置为-1.
	/*下面先找到最大可移动数位置*/
	for (int i = 0; i<n; i++)
	{
		if (A[i] < max)
			continue;
		if ((i < n - 1 && A[i] > A[i + 1] && direct[i]) || (i> 0 && A[i] >A[i - 1] && !direct[i]))
		{
			max = A[i];
			pos = i;
		}
	}
	/*下面对它进行移动*/
	if (pos == -1)
		return false;
	if (direct[pos])
	{
		swap(A[pos], A[pos + 1]);
		swap(direct[pos], direct[pos + 1]);
	}
	else
	{
		swap(A[pos], A[pos - 1]);
		swap(direct[pos], direct[pos - 1]);
	}

	/*最后调整所有比最大可移动数大的数的方向*/
	for (int i = 0; i<n; i++)
	{
		if (A[i] > max)
			direct[i] = !direct[i];
	}
	return true;
}
void Full_Array(int A[], int n)
{
	bool* direct = new bool[n]; //产生一个记录每个元素移动方向的数组
	sort(A, A + n); //将原序列变成一个升序
	for (int i = 0; i<n; i++)
    direct[i] = false;//初始化移动方向为false,表示从右向左。
	do
	{
		//Print(A, n);
		Count++;
		if (A[n - 1] == n)
			for (int i = n - 1; i>0; i--)
			{
				swap(A[i], A[i - 1]);
				swap(direct[i], direct[i - 1]); 
				//Print(A, n);
				Count++;
			}
		else
			for (int i = 0; i < n-1; i++)
			{
				swap(A[i], A[i + 1]);
				swap(direct[i], direct[i + 1]);
				//Print(A, n);
				Count++;
			}
	} while (Movable(A, direct, n));
	delete[]direct;
}



bool Next_Combination(int A[], int n, int r)
{
	int i;
	sort(A, A + r);
	for (i = r - 1; !(A[i] <n - r + i) && i > 0; i--);
	if (i == 0)
		return false;
	A[i] = A[i] + 1;
	for (int j = i + 1; j<r; j++)
		A[j] = A[i] + j - i;
	return true;
}


int main()
{
	//int A[] = { 7,6,8,3,4,5,1,2 };	
	//int A[] = { 3,6,5,1,2,4,7 };
	//int A[] = { 7,6,5,4,3,2,1 };
	//int A[] = { 1,2,3,4};
	int A[] = { 1,2,3,4,5,6,7,8,9,10,11,12};
	int n = sizeof(A) / sizeof(A[0]);

	//STL模版函数全排列:
	time_t t1 = time(NULL);	
    while (next_permutation(A, n))Count++;
	printf("%d\n", Count); Count = 0;
	//邻位对换法全排列:
	time_t t2 = time(NULL);
	Full_Array(A, n);	
	printf("%d\n", Count); Count = 0;
	//基础回溯递归全排列:
	time_t t3 = time(NULL);
	Permutation(A, 0, n);
	printf("%d\n", Count); Count = 0;
	time_t t4 = time(NULL);
	cout << "STL模版函数全排列: " << t2 - t1 << "s " << endl;
	cout << "邻位对换法全排列: " << t3 - t2 << "s " << endl;
	cout << "基础回溯递归全排列: " << t4 - t3 << "s " << endl;


	//printf("%d\n", Count);


	//递增进位制数法中序数求排位
	//int *medium = get_permutation_medium_plus(A, n);
	//Print(medium, n);
	//int rank =get_permutation_rank_plus(medium, n);
	//printf("%d\n",rank);
	//int *B = get_permutation_plus(medium, n);
	//Print(B, n);


	//原始中序数求排位
	//int *medium2 = get_permutation_medium(A, n);
	//Print(medium2, n - 1);
	//int *B2 = get_permutation(medium2, n - 1);
	//Print(B2,n);
	//int rank2 = get_permutation_rank(medium2, n);
	//printf("%d\n", rank2);


	//用STL模版函数循环求排位
	/*
	while (next_permutation(A, n)||!Count)
	{
		printf("原排列:  ");
		Print(A, n);
		printf("中介数:  ");
		int *medium = get_permutation_medium_plus(A, n);
		Print(medium, n);
		int rank = get_permutation_rank_plus(medium, n);
		printf("新序号:  %d\n", rank);
		printf("---------------\n");
		Count++;
	}
	*/
	//printf("%d\n", factorial(n)-Count-1);

	//Permutation(A, 0, n);
	//printf("%d\n", Count);


	//sort(A, A+n );
	//Print(A,n);

	//prev_permutation(A, A + n);

	/*
	Print(A, n);
	while (next_permutation(A,  n))
	{
	Count++;
	Print(A,n);
	}
	printf("%d\n", Count);
	Print(A, n);

	*/
	system("pause");
	return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值