算法设计-全排列递归

排列:从n个元素中任取m个元素,并按照必定的顺序进行排列,称为排列;ios

全排列:当n==m时,称为全排列;程序员

好比:集合{ 1,2,3}的全排列为:面试

{ 1 2 3}算法

{ 1 3 2 }windows

{ 2 1 3 }数组

{ 2 3 1 }函数

{ 3 2 1 }spa

{ 3 1 2 }.net

咱们能够将这个排列问题画成图形表示,即排列枚举树,好比下图为{1,2,3}的排列枚举树,此树和咱们这里介绍的算法彻底一致;设计

算法思路:

(1)n个元素的全排列=(n-1个元素的全排列)+(另外一个元素做为前缀);

(2)出口:若是只有一个元素的全排列,则说明已经排完,则输出数组;

(3)不断将每一个元素放做第一个元素,而后将这个元素做为前缀,并将其他元素继续全排列,等到出口,出口出去后还须要还原数组;

这里先把集合中的元素理解为不会出现重复了,那么实现的方法(C++)以下:

成员管理,互评,文件共享,事务通知
#include <iostream>
using namespace std;

int sum = 0;//记录有多少种组合

void Swap(char str[], int a, int b)
{
    char temp = str[a];
    str[a] = str[b];
    str[b] = temp;
}

void Perm(char str[], int begin, int end)
{
    if (begin == end)
    {
        for (int i = 0; i <= end; i++)
        {
            cout << str[i];
        }
        cout << endl;
        sum++;
        return;
    }
    else
    {
        for (int j = begin; j <= end; j++)
        {
            Swap(str, begin, j);
            Perm(str, begin + 1, end);
            Swap(str, j, begin);
        }
    }
}

int main()
{
    int n;
    char c[16];
    char tmp;


    cin >> n;
    tmp = getchar();    // 接受回车
    if (1 <= n && n <= 15) {
        for (int i = 0; i < n; i++) {
            c[i] = getchar();
        }
        Perm(c, 0, n - 1);
    }
    cout << sum;
    cout << endl;
    return 0;
}

实现后效果以下图:

1

有重复元素的排列问题

而后如今的题目要求是排列中的元素是包含相同元素的,给定n以及待排的n个可能重复的元素。计算输出n个元素的全部不一样排列,所以上面那个算法显然仍是不够好,由于相同的元素都当成不一样的元素,所以有了重复的排列在里面

去掉重复符号的全排列:在交换以前能够先判断两个符号是否相同,不相同才交换,这个时候须要一个判断符号是否相同的函数。也就是下面的IsSwap();

对122,第一个数1与第二个数2交换获得212,而后考虑第一个数1与第三个数2交换,此时因为第三个数等于第二个数,因此第一个数再也不与第三个数交换。再考虑212,它的第二个数与第三个数交换能够获得解决221。

去掉重复的规则:去重的全排列就是从第一个数字起每一个数分别与它后面非重复出现的数字交换。

#include <iostream>
using namespace std;

int sum=0;//记录有多少种组合

void Swap(char str[], int a, int b)
{
    char temp = str[a];
    str[a] = str[b];
    str[b] = temp;
}

bool IsSwap(char *pchar, int nBegin, int nEnd)
{
    for (int i = nBegin; i < nEnd; i++)
        if (pchar[i] == pchar[nEnd])
            return false;
    return true;
}

void Perm(char str[], int begin, int end)
{
    if (begin==end)
    {
        for (int i = 0; i <= end; i++)
        {
            cout << str[i];
        }
        cout << endl;
        sum++;
        return;
    }
    else
    {
        for (int j = begin; j <= end; j++)
        {
            if (IsSwap(str, begin, j))
            {
                Swap(str, begin, j);
                Perm(str, begin + 1, end);
                Swap(str, j, begin);
            }
        }
    }
}

int main()
{
    int n;
    char c[16];
    char tmp;


    cin >> n;
    tmp = getchar();    // 接受回车
    if (1 <= n && n <= 15) {
        for (int i = 0; i < n; i++) {
            c[i] = getchar();
        }
        Perm(c, 0, n - 1);
    }
    cout << sum;
    cout << endl;
    return 0;
}

2

 

非递归的实现

实现思路:

要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。如"1234"的下一个排列就是"1243"。只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。

如何计算字符串的下一个排列了?来考虑"926520"这个字符串,咱们从后向前找第一双相邻的递增数字,"20"、"52"都是非递增的,"26 "即知足要求,称前一个数字2为替换数,替换数的下标称为替换点,再从后面找一个比替换数大的最小数(这个数必然存在),0、2都不行,5能够,将5和2交换获得"956220",而后再将替换点后的字符串"6220"颠倒即获得"950226"。

对于像"4321"这种已是最“大”的排列,采用STL中的处理方法,将字符串整个颠倒获得最“小”的排列"1234"并返回false。

//全排列的非递归实现
#include <iostream>
using namespace std;
void Swap(char *a, char *b)
{
    char t = *a;
    *a = *b;
    *b = t;
}
//反转区间
void Reverse(char *a, char *b)
{
    while (a < b)
        Swap(a++, b--);
}
//下一个排列
bool Next_permutation(char a[])
{
    char *pEnd = a + strlen(a);
    if (a == pEnd)
        return false;
    char *p, *q, *pFind;
    pEnd--;
    p = pEnd;
    while (p != a)
    {
        q = p;
        --p;
        if (*p < *q) //找降序的相邻2数,前一个数即替换数
        {
            //从后向前找比替换点大的第一个数
            pFind = pEnd;
            while (*pFind <= *p)
                --pFind;
            //替换
            Swap(pFind, p);
            //替换点后的数所有反转
            Reverse(q, pEnd);
            return true;
        }
    }
    Reverse(p, pEnd);//若是没有下一个排列,所有反转后返回true
    return false;
}
int QsortCmp(const void *pa, const void *pb)
{
    return *(char*)pa - *(char*)pb;
}
int main()
{
    int sum = 0;
    char szTextStr[16];
    cin >> szTextStr;
    char tmp = getchar();    // 接受回车
    
    //加上排序
    qsort(szTextStr, strlen(szTextStr), sizeof(szTextStr[0]), QsortCmp);
    int i = 1;
    cout << endl << endl << endl;
    do{
        cout<<szTextStr<<endl;
        sum++;
    } while (Next_permutation(szTextStr));

    cout << sum<<endl;
    return 0;
}

 

1

总结:

排列在笔试面试中很热门,在百度和迅雷的校园招聘以及程序员和软件设计师的考试中都考到了,所以了解全排列算法对咱们都颇有好处。也是算法的一个基本思想。递归算法的思路比较直,而非递归的就比较难去想到使用这种方法来实现。

1.全排列就是从第一个数字起每一个数分别与它后面的数字交换。

2.去重的全排列就是从第一个数字起每一个数分别与它后面非重复出现的数字交换。

3.全排列的非递归就是由后向前找替换数和替换点,而后由后向前找第一个比替换数大的数与替换数交换,最后颠倒替换点后的全部数据。

 

参考文献:http://blog.csdn.net/morewindows/article/details/7370155/

相关文章
相关标签/搜索