求全排列,不重复(Permutations II)

本文介绍了两种求解集合所有不重复排列的方法:使用next_permutation函数和递归实现。next_permutation函数通过查找非升序元素并调整得到下一个字典序序列;递归方法则通过将问题转化为从不同袋子里取球来解决。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:给定一个集合包含一些整数,整数有重复,求这个集合的所有排列,排列不能有重复。


分析:

有两种方法 1. 调用next_permutation函数,直到此函数返回false。2. 用递归实现。下面分别介绍这两种方法。


1. 调用next_permutaion函数

next_permutaion函数是用来求比当前排列按字典序大的下一个序列。

我们可以从右往左遍历数组,找到从右开始第一个不按照升序排序的数字,记为x,然后再次从右往左遍历一次找到第一个比x大的数字,记为y,然后对调x和y,最后将y之后的数字全部翻转。

举例:

6,7,8,4,3,2
从右找到第一个非升序的数字7
从右找到第一个比7大的数字8
调换7和8,得到6,8,7,4,3,2
将8之后的数字全部翻转得到6,8,2,3,4,7此即为下一个字典序的序列。

代码如下:

bool next_permutation(int a[],int n){

    int pivot = -1;

    for(int i = n-1;i>0;i--){

       if(a[i-1]<a[i]){

           pivot = i-1;

           break;

       }

    }

    if(pivot == -1){

       reverse(a,n);

       return false;

    }

    int biggerThanPivot = -1;

    for(int i = n-1;i>0;i--){

       if(a[i]>a[pivot]){

           biggerThanPivot = i;

           break;

       }

    }

    swap(a[pivot],a[biggerThanPivot]);

    reverse(a+pivot+1,n-pivot-1);

    return true;

}


调用next_permutaion之前,要先对原数组排序,得到从小到大排列的数组,然后调用next_permutaion直到此函数返回false,就得到了全部的排列,且不重复。
代码如下:

void permutation(int a[],int n){

    sort(a,a+n);

    while(true){

       printArray(a,n);

       if(!next_permutation(a,n))

           break;

    }

}

 


2. 递归实现
递归实现比较复杂,首先,为了得到所有不重复的集合,我们可以这么理解这个问题,把数组看成是不同球的编号,数组的大小为球的个数,不同的数字为不同的球,比如数组【1,1,2】就代表有两个标号为1的球和一个标号为2的球,现在的问题是一共要从中取出n个球,有多少种不同的顺序。我们可以先把不同标号的球放到不同的袋子里,标号为1的球都放到标号为1的袋子里,标号为2的球都放到标号为2的袋子里,以此类推,最后我们假设得到了m个不同的带子,里面分别放了若干个球,接着我们要做的只是从这些袋子里面取n个球,一共有多少种中取法就是所有的不重复的集合数,取的过程中要注意的一个问题是从某个袋子里面取球时,要保证这个袋子里面还有剩余的球可取。

先看主函数代码:

void permutations2(int a[],int n){

    sort(a,a+n);//可有可无,排序的话可以得到每个排列重点数字都是递增排列

    unordered_map<int,int> mymap;

    for(int i = 0;i<n;i++){

       if(mymap.find(a[i]) != mymap.end())

           mymap[a[i]]++;

       else

           mymap[a[i]] = 1;

    }

    vector<pair<int,int>> elems;

    for(auto i = mymap.begin();i != mymap.end();i++)

       elems.push_back(pair<int,int>(i->first,i->second));

    //以上为把每个球放到相应的袋子中,并记录袋子中球的个数。

    vector<int> path;

    permutations2Helper(elems,path,n);

}

主要的取球算法在permutations2Helper函数中,接下来看这个函数的实现:

void permutations2Helper(vector<pair<int,int>>& elems,vector<int>& path,int n){

    if(path.size() == n){

       printVec(path);

       return;

    }

    for(size_t i = 0;i<elems.size();i++){

       int count = 0;

       for(size_t j = 0;j<path.size();j++)

           if(path[j] == elems[i].first)

              count++;

       if(count < elems[i].second){

           path.push_back(elems[i].first);

           permutations2Helper(elems,path,n);

           path.pop_back();

       }

    }

}

path是临时变量,记录当前路径即当前取球次序的变量。每次取球时,可以从任何一个袋子中取,只要袋子中的球还没被取完。当取到的球的总数等于数组大小即总球数时,返回。

 

 本人水平有限,文章有错误的地方欢迎大家指出,有问题也希望大家能够留言交流,一起学习进步

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值