现在的位置: 首页 > 综合 > 正文

n个数全排列的非递归生成算法,C实现

2013年10月04日 ⁄ 综合 ⁄ 共 2051字 ⁄ 字号 评论关闭
今年Autodesk到我们那儿招聘实习生的题目是矩阵计算。其中要用到任意阶数的矩阵求逆。凭我线性代数55分的脑袋瓜,能理解的算法也就是最笨的那种啦~中间还要求任意阶数的行列式的值,想想就头疼。结果在慌乱之间,必要的一步n个数的全排列生成也不会写了。结果自然是“可耻的失败鸟…”,一怒之下,回来补完!
第一次看到这个算法是在软件设计师的辅导书上。代码如下,在VC++ 7.0下调试通过。
 

// Permutation.cpp : 定义控制台应用程序的入口点。
//
//N个数全排列的非递归算法
 
#include "stdafx.h"
 
void swap(int &a, int &b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}
 
/*
根据当前的排列p,计算下一个排列。
原则是从1234-->4321,若p已经是最后一个排列,传回false,否则传回true。
p是一个n维向量。
*/
bool nextPermutation(int *p, int n)
{
    int last = n - 1;
    int i, j, k;
 
    //从后向前查找,看有没有后面的数大于前面的数的情况,若有则停在后一个数的位置。
    i = last;
    while (i > 0 && p[i] < p[i - 1])
       i--;
    //若没有后面的数大于前面的数的情况,说明已经到了最后一个排列,返回false。
    if (i == 0)
       return false;
 
    //从后查到i,查找大于p[i - 1]的最小的数,记入k
    k = i;
    for (j = last; j >= i; j--)
       if (p[j] > p[i - 1] && p[j] < p[k])
           k = j;
    //交换p[k]和p[i - 1]
    swap(p[k], p[i - 1]);
    //倒置p[last]到p[i]
    for (j = last, k = i; j > k; j--, k++)
       swap(p[j], p[k]);
 
    return true;
}
 
//显示一个排列
void showPermutation(int *p, int n)
{
    for (int i = 0; i < n; i++)
       cout << p[i];
}
 
int _tmain(int argc, _TCHAR* argv[])
{
    int n;
    int *p;
   
    cin >> n;
    p = new int[n];
 
    for (int i = 0; i < n; i++)
       p[i] = i + 1;
 
    showPermutation(p, n);
    cout << endl;
 
    while(nextPermutation(p, n))
    {
       showPermutation(p, n);
       cout << endl;
    }
 
    delete[] p;
 
    system("pause");
 
    return 0;
}

 

这个算法挺难理解的。这里试图说明一下算法的意思。
主要的任务在nextPermuation()中完成。这其中的思想是,提供一个已经有的全排列P,求出P的“下一个”全排列。这里“下一个”的意思是说,在n个数的n!个全排列在数字上从小到大的一个序列中,p的下一个数字上比它大的一个排列。
那么由此,提供一个初始的n个数的全排列P1 = p1p2…pn,有pi > pi – 1(1 < i < n – 1),反复运行nextPermuation()找出比P的“下一个”全排列,直到Pn! = p1’p2’…pn’,pi < pi – 1
(1 < i < n – 1)为止。所找到的所有的排列就是n的全排列。
下面要考虑的问题,是如何从一个已知的排列P = p1p2…pn,找到它的下一个排列
Q = q1q2…qn。我们要让排列从小到大生成,简单说,要让排列的趋势从p1p2…pn,pi > pi – 1(1 < i < n – 1),到p1’p2’…pn’,pi < pi – 1(1 < i < n – 1)。因此:
1.          从低位到高位(从后向前),找出“不符合趋势”的数字。即找到一个pi,使pi – 1 < pi。若找不到这样的pi,说明我们已经找到最后一个全排列,可以返回了。
2.          把pi - 1替换成从pn到pi几个数字中“刚刚好大于pi-1”的数字。这里的目的是找出准确的P的下一个排列Q。所谓“刚刚好大于”,我们就查找从pi到pn中大于pi-1的最小的数字。然后将找到的数字与pi-1交换。
3.          还没有结束。交换后,pi-1pi…pn并不是准确的后一个排列。因为根据第一步的查找,我们有pi > pi+1 > … > pn,否则查找在i~n就会停下来了。这样的一个排列显然不是最小的。实际上,原来的pi…pn,已经是这一部分最大的一个排列了。但我们现在换了最高位pi-1,因此要让后面的数字变的最小。方法很简单,根据上面的推理,我们只须将pi~pn的数列倒置即可(最大的排列倒置就变成了最小的排列)。
这样,我们计算出了准确的下一个排列。

抱歉!评论已关闭.