`
helpbs
  • 浏览: 1163013 次
文章分类
社区版块
存档分类
最新评论

有序全排列生成算法集锦

 
阅读更多

/*

Name: 有序全排列生成算法集锦

Copyright: 始发于goal00001111的专栏;

Description: 实现了五种有序全排列生成算法。

有关算法的分析讨论详见拙作《有序全排列生成算法》:

http://blog.csdn.net/goal00001111/archive/2008/11/18/3326619.aspx

*/

#include<iostream>

#include <time.h>

using namespace std;

void Permutation_1(long long n, long long m);//递归直接模拟法

void Permutation_2(long long n, long long m);//循环直接模拟法

void Permutation_3(long long n, long long m);//邻元素增值法

void Permutation_4(long long n, long long m);//设置中介数的字典序全排列生成法

void Permutation_5(long long n, long long m);//数学分析法

bool Try(long long a[], int left, int right, long long m, long long & s);

void MoveRight(long long a[], int left, int right);

void MoveLeft(long long a[], int left, int right);

bool IsRight(long long a[], int n);

void DiZeng(int dZ[], long long p[], int len, long long m);

void YingShe(int yShe[], int dZJ[], int len);

int Compare(long long p[], int n, long long m);

void Move(long long a[], int left, int pos);

void Reverse(long long a[], int left, int right);

int main()

{

long long m , n;

time_t startTime;

time_t endTime;

n = 12;

time(&startTime);

for(m=1000; m<10000001; m*=100)

Permutation_1(n, m); //递归直接模拟法

time(&endTime);

cout << "time 1 = " << difftime(endTime, startTime) << endl << endl;

time(&startTime);

for(m=1000; m<10000001; m*=100)

Permutation_2(n, m); //循环直接模拟法

time(&endTime);

cout << "time 2 = " << difftime(endTime, startTime) << endl << endl;

time(&startTime);

for(m=10; m<100001; m*=100)

Permutation_3(n, m); //邻元素增值法

time(&endTime);

cout << "time 3 = " << difftime(endTime, startTime) << endl << endl;

time(&startTime);

for(m=1000; m<10000001; m*=100)

Permutation_4(n, m); //设置中介数的字典序全排列生成法

time(&endTime);

cout << "time 4 = " << difftime(endTime, startTime) << endl << endl;

time(&startTime);

for(m=1000; m<10000001; m*=100)

Permutation_5(n, m); //数学分析法

time(&endTime);

cout << "time 5 = " << difftime(endTime, startTime) << endl << endl;

system("pause");

return 0;

}

////////////////////////////////////////////////////////////////////////

/*

函数名称:Permutation

函数功能:输出n个数的第m种全排列

输入变量:long long n:1,2,3,...,n共n个自然数(1<=n<=20)

long long m:第m种全排列(1<=m<=n!)

输出变量:无

*/

void Permutation_1(long long n, long long m)//递归直接模拟法

{

long long *a = new long long[n];//用来存储n个自然数

for (int i=0; i<n; i++)

{

a[i] = i + 1;

}

long long s = 0;//记录当前是第几个全排列

Try(a, 0, n-1, m, s);//递归查找并输出n个数的第m种全排列

delete []a;

}

/*

函数名称:Try

函数功能:递归查找并输出n个数的第m种全排列 ,找到第m种全排列返回true,否则返回false

输入变量:long long a[]:用来存储n个自然数,按照第m种全排列存储

int left:数组的左界

int right:数组的右界

long long m:第m种全排列(1<=m<=n!)

long long & s:记录当前是第几个全排列

输出变量:找到第m种全排列返回true,否则返回false

*/

bool Try(long long a[], int left, int right, long long m, long long & s)

{

if (left == right)//已经到达数组的右界,直接输出数组

{

s++;

if (s == m)//找到第m种全排列

{

cout << s << ": ";

for (int i=0; i<=right; i++)

{

cout << a[i] << ' ';

}

cout << endl;

return true;

}

}

else

{ //将当前最左边的元素与其后面的元素交换位置

//当i=left时,与自身交换,相当于不换,直接输出

for (int i=left; i<=right; i++)

{

MoveRight(a, left, i);

if (Try(a, left+1, right, m, s))

{

return true;

}

MoveLeft(a, left, i);

}

}

return false;

}

//将right位置的元素左移到left位置,其他元素按顺序右移

void MoveRight(long long a[], int left, int right)

{

long long temp = a[right];

for (int i=right; i>left; i--)

{

a[i] = a[i-1];

}

a[left] = temp;

}

//将left位置的元素右移到right位置,其他元素按顺序左移

void MoveLeft(long long a[], int left, int right)

{

long long temp = a[left];

for (int i=left; i<right; i++)

{

a[i] = a[i+1];

}

a[right] = temp;

}

//////////////////////////////////////////////////////////////////////////////

void Permutation_2(long long n, long long m)//循环直接模拟法

{

long long *a = new long long[n];//用来存储n个自然数

for (int i=0; i<n; i++) //存储全排列的元素值,并计算全排列的数量

{

a[i] = i + 1;

}

cout << m << ": "; //输出n个数的第m种全排列

int left, right;

long long temp;

for (int i=1; i<m; i++)

{

left = n - 2; //设置需要改变的元素的左边界,保证此时左边界右边的元素是一个递减排列

while (left >= 0 && a[left] > a[left+1])

left--;

right = n - 1;

while (a[right] < a[left])//找到左边界右边数组中比a[left]大的最小的元素,这个就是要取代a[left]的元素

right--;

temp = a[left]; a[left] = a[right]; a[right] = temp; //交换a[right]和a[left]

left++; //左边界增1,保证此时左右边界之间的元素是一个递减排列

right = n -1;

while (left < right)//逆置左右边界之间的元素,使其按增序排列

{

temp = a[left]; a[left] = a[right]; a[right] = temp;

left++;

right--;

}

}

for (int i=0; i<n; i++)

{

cout << a[i] << " ";

}

cout << endl;

delete []a;

}

//////////////////////////////////////////////////////////////////////////////////////////////////////

void Permutation_3(long long n, long long m)//邻元素增值法

{

long long *a = new long long[n];//用来存储n个自然数

for (int i=0; i<n; i++) //存储全排列的元素值

{

a[i] = i + 1;

}

cout << m << " : ";

long long s = 1;

int pos;

while (s < m)

{

pos = n - 1; //最后一位加n-1

a[pos] = (a[pos] + pos);

while (pos > 0 && a[pos] > n)//若有进位,前一元素加1,直到没有进位为止

{

a[pos--] -= n;//大于n则取余数,实际上a[pos]一定比2*n小

a[pos] += 1; //前一元素加1

}

if (a[0] > n)//当第一个元素的值>n则结束

break;

if (IsRight(a, n))//若数组a中不存在重复元素,说明得到了一个正确的全排列

{

s++;

}

}

for (int i=0; i<n; i++)

cout << a[i] << ' ';

cout << endl;

delete []a;

}

bool IsRight(long long a[], int n)

{

for (int i=0; i<n; i++)

{

for (int j=i+1; j<n; j++)

{

if (a[i] == a[j])

return false;

}

}

return true;

}

////////////////////////////////////////////////////////////////////////////////////////

void Permutation_4(long long n, long long m)//设置中介数的字典序全排列生成法

{

int *a = new int[n];//用来存储n个自然数,也可以看成是初始全排列1234...n

for (int i=0; i<n; i++)

{

a[i] = i + 1;

}

long long s = 1;

long long *p = new long long[n];//用来存储各个全排列的值,p[i]=i!

for (int i=0; i<n; i++)

{

s *= a[i];

p[i] = s;

}

cout << m << ": "; //输出n个数的第m种全排列

int *diZ = new int[n-1]; //用来存储序号m的递增进进制数,设所有元素的初值为0

for (int i=0; i<n-1; i++)

{

diZ[i] = 0;

}

DiZeng(diZ, p, n-1, m-1);//获取序号m-1的递增进进制数

int *zhongJie = new int[n-1];

for (int i=0; i<n-1; i++)//设初始全排列的中介数为0

zhongJie[i] = 0;

YingShe(zhongJie, diZ, n);//将原中介数加上序号m-1的递增进进制数得到新的映射

//将映射还原,得到新的全排列b

int *b = new int[n];//用来存储新的全排列

for (int i=0; i<n-1; i++) //获取前n-1个数字

{

s = 0;

int j = 0;

while (s < zhongJie[i])

{

if (a[j] != -1)

s++;

j++;

}

while (a[j] == -1)

j++;

b[i] = a[j];

a[j] = -1; //用-1表示该位置已经被填充

}

for (int i=0; i<n; i++)//填充最后一个数字

{

if (a[i] != -1)

{

b[n-1] = a[i];

break;

}

}

for (int i=0; i<n; i++)

{

cout << b[i] << ' ';

}

cout << endl;

delete []a;

delete []b;

delete []p;

delete []diZ;

delete []zhongJie;

}

void YingShe(int yShe[], int dZJ[], int len)

{

int pos = len - 2;

int r = 0;

while (pos >= 0)

{

yShe[pos] += dZJ[pos] + r;

if (yShe[pos] >= len - pos)

{

yShe[pos] -= len - pos;

r = 1;

}

else

r = 0;

pos--;

}

}

void DiZeng(int dZ[], long long p[], int len, long long m)

{

int pos = 0;

while (m > 0)

{

while (m < p[len-1-pos])

{

dZ[pos++] = 0;

}

if (m == p[len-1-pos])

{

dZ[pos++] = 1;

while (pos < len)

dZ[pos++] = 0;

break;

}

else

{

dZ[pos] = m / p[len-1-pos];

m %= p[len-1-pos];

pos++;

}

}

}

/////////////////////////////////////////////////////////////////////////////

void Permutation_5(long long n, long long m)//数学分析法

{

long long *a = new long long[n];//用来存储n个自然数

for (int i=0; i<n; i++)

{

a[i] = i + 1;

}

long long s = 1;

long long *p = new long long[n];//用来存储各个全排列的值,p[i]=i!

for (int i=0; i<n; i++)

{

s *= a[i];

p[i] = s;

}

cout << m << ": "; //输出n个数的第m种全排列

while (m > 1)//通过判断m的值排列数组a的元素

{

//若m=1,什么也不做,直接输出数组

if (m == 2) //若只要求输出第2个排列,直接交换数组最后两个元素

{

long long temp = a[n-1];

a[n-1] = a[n-2];

a[n-2] = temp;

break; //跳出循环

}

else if (m > 2)

{

int pos = Compare(p, n, m);//查找pos,使得p[pos-1] < m <= p[pos]

int left = n - 1 - pos;//由于m<p[pos],所以前n-1-pos个元素不必移动,故让数组的左界移至n-1-pos位

if (p[pos] == m)//如果p[pos] == m,直接逆置数组

{

Reverse(a, left, n-1);//逆置数组

break; //跳出循环

}

else

{

int r = m / p[pos-1] + left;//计算排列在left位的元素位置

m %= p[pos-1]; //改变m的值----这是本算法的关键!

if (m == 0)//如果m是p[pos-1]的倍数,将第r-1位元素移动到left位

{

Move(a, left, r-1);

Reverse(a, left+1, n-1);//逆置数组

}

else//否则将第r位元素移动到left位,并排列剩下的数组元素

{

Move(a, left, r);

}

}

}

}

for (int i=0; i<n; i++)

{

cout << a[i] << ' ';

}

cout << endl;

delete []a;

delete []p;

}

/*

函数名称:Reverse

函数功能:逆置数组

输入变量:long long a[]:逆置前的数组

int left:数组的左界

int right:数组的右界

输出变量:long long a[]:逆置后的数组

*/

void Reverse(long long a[], int left, int right)

{

while (left < right)

{

long long temp = a[left];

a[left++] = a[right];

a[right--] = temp;

}

}

/*

函数名称:Compare

函数功能:寻找使得p[pos-1] < m <= p[pos]的数组下标pos,确保m<=p[n-1]

输入变量:long long p[]:数组

int n:数组的长度

int m:用来比较的数m

输出变量:满足条件的数组下标pos

*/

int Compare(long long p[], int n, long long m)

{

for (int i=0; i<n; i++)//确保m<=p[n-1]

{

if (p[i] >= m)

{

return i;

}

}

}

/*

函数名称:Move

函数功能:将pos位的元素向左移动到left位,而left~(pos-1)位的元素则依次右移一位

输入变量:long long p[]:数组

int left:数组的左界

int pos:需要左移的位置

输出变量:满足条件的数组下标pos

*/

void Move(long long a[], int left, int pos)

{

long long temp = a[pos];//先将a[pos]存储起来

for (int i=pos; i>left; i--)//left~(pos-1)位的元素则依次右移一位

{

a[i] = a[i-1];

}

a[left] = temp;//pos位的元素移动到left位

}

//////////////////////////////////////////////////////////////////////////////////


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Deutschester/archive/2009/06/24/4295684.aspx

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics