/*
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/goal00001111/archive/2008/11/18/3326642.aspx