生成全排列的两种方法

问题定义

给定一个集合{a1, a2, ..., an}, 要求输出集合中元素的所有排列。
例如: 集合{1,3}的全排列有: {1,3 }, {3,1}

解决方案

对于全排列问题,已经存在很多递归和非递归的算法来解决这个问题,作为学习笔记,我这里只列举两种比较经典且易懂的算法。有兴趣的同学可以参考以下链接

方法(1)基于交换的递归方法: 求n个元素的全排列可以先从n个元素中选一个作为首元素,然后排列剩下的元素的全排列。注意 一个元素的全排列是其本身。

    例:  Perm({a, b, c} = {a}.Perm({b, c}) + {b}.Perm({a, c}) + {c}.Perm({a, b})

  
/**
*@brief: 元素全排列-基于交换的递归实现
*
*@idea: 求n个元素的全排列可以选从n个中选一个作为首元素,然后排列剩下的元素的全排列。
*      一个元素的全排列是其本身。
*
*@note: 原算法没有考虑到元素重复会导致生成的排列重复的情况。
*       改进的方法是在每一论选取一个元素作为首元素时,先判断下这个元素是否已经选取过。
*
**/

#include<iostream>
using namespace std;


/
/**
*@brief: Swap two elements
*/
template<class T>
void Swap(T &a, T &b)
{
    T temp(a);

    a = b;
    b = temp;
}


/**
*@brief: print all premutation of the given array
*@param A: array
*@param size: the size of the array
*@param n: the start index of the sub array to permutate
*/
template<class T>
void Permutation(T A[], int size, int n)
{
    if (n == size - 1)
    {
        for (int i=0; i<size; ++i)
        {
            cout<<A[i]<<" ";
        }
        cout<<endl;
    }

    for (int i=n; i<size; ++i)
    {
        //avoid repeated permutations
        int k;
        for (k=n; k<i; ++k)
        {
            if (A[k] == A[i]) break;
        }
        if (k < i) continue;

        Swap(A[n], A[i]);
        Permutation(A, size, n+1);
        Swap(A[n], A[i]);
    }
}


//


int main()
{

    int Arr[] = {1, 3, 3, 4, 5};

    Permutation(Arr, 5, 0);

    return 0;
}

值得注意的是, 原算法思想没有考虑到元素重复会导致生成的排列重复的情况。 改进的方法是在每一轮选取元素作为首元素时,先判断下这个元素是否已经选取过, 具体见代码。


方法(2)基于字典序法的非递归实现: 给定一个初始排列,通过字典序的转换规则不断得到它的下一个排列。 如果初始排列是从小到大的有序排列,那么最后能得到全排列。

得到下一个排列的方法:

*step 1: 从右到左扫描集合,找到第一个小于其右边元素的元素的下标i

*step 2: 从右往左扫描元素, 找到第一个大于A[i]的元素的下标j

*step 3: 交换 A[i] A[j]

*step4 : 反转A[i+1 ~ n]


/**
*@brief: 元素全排列-基于字典序法的非递归实现
*
*@idea:  给定一个初始排列,通过字典序的转换规则不断得到它的下一个排列。
*        如果初始排列是从小到大的有序排列,那么最后能得到全排列。
*@note:
        (1)这种方法不会出现重复的排列,即使给定元素集合中有重复元素
        (2)要得到所有排列, 元素必须先从小到大排序一遍

*@complexity: O(n*n!)
**/

#include<iostream>
#include<algorithm>
using namespace std;


//

/**
*@brief: swap two elements
**/
template<class T>
void Swap(T &a, T &b)
{
    T temp(a);
    a = b;
    b = temp;
}


/**
*@brief: get next permutation
*step 1: 从右到左扫描集合,找到第一个小于其右边元素的元素的下标i
*step 2: 从右往左扫描元素, 找到第一个大于A[i]的元素的下标j
*step 3: 交换 A[i] A[j]
*step4 : 反转A[i+1 ~ n]
*@return bool: indicate whether has next permutation
*/
template<class T>
bool Next_perm(T A[], int size)
{
    int i, j;

    //step 1
    for (i=size-2; i>=0; --i)
    {
        if (A[i] < A[i+1]) break;
    }

    //if is the last permuation
    if (i < 0) return false;


    //step 2
    for (j = size-1; j>=0; --j)
    {
        if (A[j] > A[i])
        {
            //step3
            Swap(A[i], A[j]);
            break;
        }
    }

    //step 4
    while (++i < --size)
    {
        Swap(A[i], A[size]);
    }

    return true;
}

/**
*@brief: print all permutations of the given array
*/
template<class T>
void Permutation(T A[], int size)
{
    do
    {
        //print the current permutation
        for (int i=0; i<size; ++i)
        {
            cout<<A[i]<<" ";
        }
        cout<<endl;

    }while (Next_perm(A, size));
}

///


int main()
{

    int Arr[] = {3, 1, 3, 4, 5};

    sort(Arr, Arr+5);

    Permutation(Arr, 5);

    return 0;
}

应用这个非递归算法要注意以下两点:

(1)这种方法不会出现重复的排列,即使给定元素集合中有重复元素

(2)要得到所有排列, 元素必须先从小到大排序一遍




*****作为我在CSDN上的开篇博客,在这里留个纪念。希望各位大神多多指教,写得不对之处,在希望各位秉承善意地指出,谢谢!*************



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值