手记

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

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


0人推荐
随时随地看视频
慕课网APP