有序全排列生成算法集锦

来源:互联网 发布:摇钱树软件 编辑:程序博客网 时间:2024/05/18 02:21

/*

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

 Copyright: 始发于goal00001111的专栏;允许自由转载,但必须注明作者和出处

 Author: goal00001111

 Date: 18-11-08 15:50

  

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 longm);//递归直接模拟法

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

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

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

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

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

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

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

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

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

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

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

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

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

 

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");

       return0;

}

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

/*

函数名称:Permutation

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

输入变量:long longn123...nn个自然数(1<=n<=20

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

输出变量:无

*/

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

{

   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 longa[]:用来存储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, intright, 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, intright)

{

   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, intright)

{

   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 longm)//循环直接模拟法

{

   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 longm)//邻元素增值法

{

   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 longm)//设置中介数的字典序全排列生成法

{

   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[], intlen)

{

   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[], intlen, 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 longm)//数学分析法

{

   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)//如果mp[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 longa[]:逆置前的数组

         int left:数组的左界

         int right:数组的右界

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

*/

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

{

   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 longp[]:数组

         int n:数组的长度

         int m:用来比较的数m

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

*/

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

{

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

    {

       if (p[i] >= m)

       {

           return i;

       }

    }

}

/*

函数名称:Move

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

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

         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

}

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

原创粉丝点击