生成全排列的两种方法

问题定义

给定一个集合{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)要得到所有排列, 元素必须先从小到大排序一遍




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



### C++ 实现全排列算法 以下是基于标准库函数 `std::next_permutation` 和递归方法(回溯法)两种方式实现的 C++ 全排列生成代码。 #### 方法一:使用标准库函数 `std::next_permutation` 通过调用 `<algorithm>` 头文件中的 `std::next_permutation` 函数可以轻松生成给定序列的所有排列组合[^1]。该函数会将当前排列修改为其字典序下的下一个排列,直到无法继续为止。 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> nums = {1, 2, 3}; // 输入数组 sort(nums.begin(), nums.end()); // 确保初始状态是最小字典序排列 do { for (auto num : nums) { cout << num << " "; } cout << endl; } while (next_permutation(nums.begin(), nums.end())); // 循环至最后一个排列 return 0; } ``` 此程序首先对输入数据进行排序以获得最小字典顺序排列,随后利用循环不断调用 `std::next_permutation` 来打印每一个新的排列直至结束。 --- #### 方法二:递归法(回溯) 另一种常见的方式是采用递归加回溯的思想来手动构建所有可能的排列情况[^4]。这种方法不需要依赖额外的标准库支持,适合深入学习算法原理的人群。 ```cpp #include <iostream> #include <vector> using namespace std; void backtrack(vector<vector<int>>& res, vector<int>& output, int first, int len) { if (first == len) { // 当前排列完成 res.emplace_back(output); return; } for (int i = first; i < len; ++i) { swap(output[first], output[i]); // 动态维护数组 backtrack(res, output, first + 1, len); // 继续递归填下一个数 swap(output[first], output[i]); // 撤销操作恢复原状 } } vector<vector<int>> permute(vector<int>& nums) { vector<vector<int>> result; backtrack(result, nums, 0, static_cast<int>(nums.size())); return result; } int main() { vector<int> nums = {1, 2, 3}; vector<vector<int>> permutations = permute(nums); for (const auto& p : permutations) { for (int n : p) { cout << n << ' '; } cout << endl; } return 0; } ``` 上述代码定义了一个辅助函数 `backtrack` 负责执行实际的递归逻辑,并最终返回存储有全部排列的结果列表。 --- #### 方法三:协程版全排列 对于更高级的应用场景还可以考虑引入协程技术来自动生成并处理大规模的数据集而不占用过多内存资源[^2]。不过需要注意的是目前主流编译器对该特性的支持程度可能存在差异,请谨慎选用。 由于篇幅原因这里不再提供具体实现细节,有兴趣者可查阅相关资料进一步了解其工作机制与优势所在。 --- ### 结论 综上所述,在C++中可以通过多种途径达成生成指定集合元素所有可能排列的目标。无论是借助内置工具还是自行编写核心业务流程均各有千秋值得探索实践一番!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值