算法设计-全排列递归

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

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

比如:集合{ 1,2,3}的全排列为:

{ 1 2 3}

{ 1 3 2 }

{ 2 1 3 }

{ 2 3 1 }

{ 3 2 1 }

{ 3 1 2 }

我们可以将这个排列问题画成图形表示,即排列枚举树,比如下图为{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;
}

实现后效果如下图:

有重复元素的排列问题

然后现在的题目要求是排列中的元素是包含相同元素的,给定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;
}

非递归的实现

实现思路:

要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。如"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.全排列就是从第一个数字起每个数分别与它后面的数字交换。

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

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

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

时间: 2024-09-30 09:03:25

算法设计-全排列递归的相关文章

算法设计方法:递归的内涵与经典应用

摘要: 大师 L. Peter Deutsch 说过:To Iterate is Human, to Recurse, Divine.中文译为:人理解迭代,神理解递归.毋庸置疑地,递归确实是一个奇妙的思维方式.对一些简单的递归问题,我们总是惊叹于递归描述问题的能力和编写代码的简洁,但要想真正领悟递归的精髓.灵活地运用递归思想来解决问题却并不是一件容易的事情.本文剖析了递归的思想内涵,分析了递归与循环的联系与区别,给出了递归的应用场景和一些典型应用,并利用递归和非递归的方式解决了包括阶乘.斐波那契

算法设计:全排列算法代码实现

在上星期的算法设计课程的学习中,我们学习了两种全排列算法,该算法用于求出数组{1,2,3,...,n}的所有可能的排列,今天我们就来看看这个算法的具体代码实现. 1. 第一种算法 第一种算法和我们现实生活中习惯的方法较为相似,以{1,2,3}为例,我们先写出第一种排列123,然后将2与3交换,得到132:再回到123,交换1与2得到213,再将1与3交换.....直到得到所有的排列. 该算法伪码如下: PERMUTATIONS1(int n): for j←1 to n a[j]←j end f

重拾算法之路——递归与分治基础

***************************************转载请注明出处:http://blog.csdn.net/lttree******************************************** 这个周末家里有点事,回了趟家,就断了一些学习计划.. 抓紧补上! 第一个算法--递归与分治 都知道,分治算法基本思想是 将一个难以直接解决的问题,分割成一些规模小的相同问题,以便各个击破,分而治之, 这样,我们就可以将一个复杂的算法,类型不变,规模越来越小,最终

【转载】算法设计之五大常用算法设计方法总结

转载自http://blog.csdn.net/zolalad/article/details/11393915 算法设计之五大常用算法设计方法总结 一.[分治法]  在计算机科学中,分治法是一种很重要的算法.字面上的解释是"分而治之",就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题--直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并.这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)--等.任

《PHP程序员面试笔试宝典》——如何回答算法设计问题?

如何巧妙地回答面试官的问题? 本文摘自<PHP程序员面试笔试宝典> 程序员面试中的很多算法设计问题,都是历年来各家企业的"炒现饭",不管求职者以前对算法知识掌握得是否扎实,理解得是否深入,只要面试前买本<程序员面试笔试宝典>,应付此类题目完全没有问题.但遗憾的是,很多世界级知名企业也深知这一点,如果纯粹是出一些毫无技术含量的题目,对于考前"突击手"而言,可能会占尽便宜,但对于那些技术好的人而言是非常不公平的.所以,为了把优秀的求职者与一般的求

算法设计与分析 - 李春葆 - 第二版 - pdf-&gt;word v3

1 1.1 第1章─概论 2 3 1.1.1 练习题 4 1. 下列关于算法的说法中正确的有( ). 5 Ⅰ.求解某一类问题的算法是唯一的 6 Ⅱ.算法必须在有限步操作之后停止 7 Ⅲ.算法的每一步操作必须是明确的,不能有歧义或含义模糊 8 Ⅳ.算法执行后一定产生确定的结果 9 A. 1个 B.2个 C.3个 D.4个 10 2. T(n)表示当输入规模为n时的算法效率,以下算法效率最优的是( ). 11 A.T(n)= T(n-1)+1,T(1)=1 B.T(n)= 2n2 12 C.T(n)

(转)常用的算法设计与分析-一夜星辰的博客

算法设计与分析 分治法 思想 1. 将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相同.递归地解这些子问题,然后将各子问题的解合并得到原问题的解. 2. divide-and-conquer(P) { if(|P| <= n0)adhoc(P); divide P into samller subinstances P1,P2...,Pk; for(int i = 1;i < k;i++) { yi = divide-and-conquer(Pi); } retu

全排列(递归与非递归实现)

全排列问题在公司笔试的时候很常见,这里介绍其递归与非递归实现. 递归算法 1.算法简述 简单地说:就是第一个数分别以后面的数进行交换 E.g:E = (a , b , c),则 prem(E)= a.perm(b,c)+ b.perm(a,c)+ c.perm(a,b) 然后a.perm(b,c)= ab.perm(c)+ ac.perm(b)= abc + acb.依次递归进行. void swap(string &pszStr,int k,int m) { if(k==m) return ;

《算法之道》精华 算法设计部分

<算法之道>精华 算法设计部分 本书作者邹恒明,作者另有一本书<数据结构之弦>,以及<操作系统之哲学原理>都是非常好的书 这本书能够算得上是深入浅出.文笔非常好,作者加入了非常多自己的思考 本文仅包含算法设计部分,算法分析略去,并没有严格依照章节顺序来记录 附录 算法随想 有人喜欢遍历,希望踏遍千山万水,人生丰富多彩:有人一生贪婪,眼界不宽,及时行乐:有人注定穷搜,辛辛苦苦,收获有限:有人善用时空均衡,用最少的时间办最多的事情.十分精明:有人会分治,再难的问题也能解决.