递归分治 --- 例题1.全排列

本文详细介绍了如何使用递归分治思想解决全排列问题,包括无重复元素和有重复元素的情况。在处理有重复元素时,提出了确保每次填入的数是重复数集合中从左往右第一个未被填过的数,从而避免重复的全排列。并通过示例代码展示了如何在C++中实现这一算法。
摘要由CSDN通过智能技术生成

递归分治 — 例题1.全排列


一.问题描述

设计一个递归算法生成n个元素{r1, r2, … , rn}的全排列.
此题与力扣主站第46题 — 全排列相同,以及力扣主站第47题 — 全排列Ⅱ

二.解题思路

设R={r1,r2…,rn}是要进行排列的n个元素,Ri=R-{ri}.集合X中的元素的全排列记为Perm(X).(ri)Perm(X)表示在全排列X的每个排列前加上前缀ri得到的排列.

R的全排列可归纳定义为如下:

  • 当n==1时,Perm®=®,其中r是集合R中唯一的元素.
  • 当n > 1时,Perm®由(r1)Perm(R1),(r2)Perm(r2),…,(rn)Perm(rn)构成

通过上述分析,我们可以很清晰地看到,规模较大的问题可以划分为规模较小的问题来,并且总问题的解可以由每个小问题的解合并而来.
有了这个性质,那么我们很自然地就可以联系上递归分治思想.

代码如下:

void Perm(T list[], int low, int high)
{
    if(low == high)		//表示已经填到最后一个字符,输出答案
    {
        for(int i=0; i<=high; ++i)
        {
            cout<<list[i];
            cout.width(4);
        }
        cout<<'\n';
    }
    for(int i=low; i<=high; ++i)  //0~low为已经填好的字符,我们在low~high中选择新字符填入,即将list[i]与list[low]交换位置
    {
        swap(list[i], list[low]);
        Perm(list, low+1, high);  //继续递归填写下一个字符,参数low变为low+1
        swap(list[i], list[low]); //递归回来之后记得交换回来,恢复原样
    }
}

下面我们更深一步,考虑一下如果有重复元素在数组中应该怎么办?

由于全排列就是从第一个数字起每个数分别与它后面的数字交换.我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这二个数就不交换了.
如122,第一个数与后面交换得212、221.然后122中第二数就不用与第三个数交换了,但对212,它第二个数与第三个数是不相同的,交换之后得到221,与由122中第一个数与第三个数交换所得的221重复了.所以这个方法不行.

换种思维,对122,第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,

此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换.再考虑212,它的第二个数与第三个数交换可以得到解决221.此时全排列生成完毕.

这样我们也得到了在全排列中去掉重复的规则——去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换.用编程的话描述就是第i个数与第j个数交换时,要求[i,j)中没有与第j个数相等的数.即保证每次填入的数一定是这个数所在重复数集合中「从左往右第一个未被填过的数.
判断在str数组中,[nBegin,nEnd]中是否有数字与下标为nEnd的数字相等

代码如下:

#include<bits/stdc++.h>
using namespace std;
bool IsSwap(char *str, int nBegin, int nEnd)  //保证每次填入的数一定是这个数所在重复数集合中「从左往右第一个未被填过的数字」
{
    for(int i=nBegin; i<nEnd; i++)
        if(str[i]==str[nEnd]) return false;
    return true;
}
// k表示当前选取到第几个数,m表示数组大小
void AllRange(char *str, int k, int m)
{
    if(k==m)
    {
        static int count = 1;
        string ans = "";
        for(int i=0; i<m; i++) ans += str[i];
        cout<<"第"<<count++<<"个排列是"<<ans<<endl;
    }
    else 
    {
        for(int i=k; i<m; i++)
        {
            if(IsSwap(str, k, i))  //同一层中相同的元素只有第一个有用
            {
                swap(str[i], str[k]);
                AllRange(str, k+1, m);
                swap(str[i], str[k]);
            }
        }
    }
}
int main()
{
    // cout<<"数组中没有重复元素的全排列:"<<endl;
    // int n;
    // cout<<"请输入数组大小:";
    // while(cin>>n && n!=0)
    // {
    //     cout<<"请输入数组元素:"<<endl;
    //     int *a = new int[n];
    //     for(int i=0; i<n; i++) cin>>a[i];
    //     Perm(a, 0, n);
    //     cout<<"请输入数组大小:";
    // }

    cout<<"数组中有重复元素的全排列:"<<endl;
    int n;
    cout<<"请输入数组大小:";
    while(cin>>n && n!=0)
    {
        cout<<"请输入数组元素:"<<endl;
        char *a = new char[n];
        for(int i=0; i<n; i++)  cin>>a[i];
        AllRange(a, 0, n);
        cout<<"请输入数组大小:";
    }
    system("pause");
    return 0;
}

运行结果:(第一幅图为没有重复元素的全排列,第二幅图为有重复元素的全排列(数据太猛了,13个字符的全排列跑了很久))

参考毕方明老师《算法设计与分析》课件.

欢迎大家访问个人博客网站—乔治的编程小屋,和我一起为大厂offer努力!

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值