排列组合
排列组合是组合学最基本的概念。所谓排列,就是指从给定个数的元素中取出指定个数的元素进行排序。组合则是指从给定个数的元素中仅仅取出指定个数的元素,不考虑排序。
公式和定义
排列
排列的定义:从n个不同元素中,任取m(m≤n,m与n均为自然数,下同)个不同的元素按照一定的顺序排成一列,叫做从n个不同元素中取出m个元素的一个排列;从n个不同元素中取出m(m≤n)个元素的所有排列的个数,叫做从n个不同元素中取出m个元素的排列数,用符号 A(n,m)表示。注意:此外规定0! = 1。
组合
组合的定义:从n个不同元素中,任取m(m≤n)个元素并成一组,叫做从n个不同元素中取出m个元素的一个组合;从n个不同元素中取出m(m≤n)个元素的所有组合的个数,叫做从n个不同元素中取出m个元素的组合数。用符号 C(n,m) 表示。C(n,m)=C(n,n-m)(n≥m)
其他排列与组合公式 从n个元素中取出m个元素的循环排列数=A(n,m)/m=n!/m(n-m)!. n个元素被分成k类,每类的个数分别是n1,n2,…nk这n个元素的全排列数为 n!/(n1!×n2!×…×nk!). k类元素,每类的个数无限,从中取出m个元素的组合数为C(m+k-1,m)。
设计思路
- 组合:从n个数(可能有重复)中取出m个不同的数(这里数是指的元素),取出的数字中可能出现值相同的情况。如同在1,2,2,5中取到1,2,2和2,2,1;
- 组合的去重:1,2,2和2,2,1这两种组合对于排序来说就像是涂有两种颜色的两堆球,颜色1有一个,颜色2有两个。排序结果二者重复,所以在相同的组合里只留下一个用作排序;
- 排列的去重:虽然在组合选择时已经进行了去重处理,但是包含重复的数字,也会导致遍历的时候产生重复序列,所以我们要对重复的排列序列进行排除,首先我们对数组进行排序,判断当前遍历到的数是否与前一个数相同,使用一个布尔型标记数组used来判断前一个相同的值是否被使用了,若正在被使用,说明正在处于前一个值得递归过程中,当前值不能被跳过;若没有被使用,说明这个值已经作为头部被使用过了,跳过当前值以排除重复。使用组合数组同类型的tmp来记录之前排列好的值,需要注意的是,遍历完成一个排列之后,需要还原tmp以进行下一次排列。
- 将所有组合的排列结果整合在一起,完成n个数(可能重复)中取m个不同数(这里的数是指元素)的排列组合。
代码实现
MyPerCom.h
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class MyPerCom
{
private:
vector<vector<int>> m_pun;
vector<vector<int>> m_com;
vector<vector<int>> m_con;
public:
//回溯,无重复排列
void Do_PermuteUnique(vector<int>& tmp, vector<bool>& used, vector<int>& nums) {
if (tmp.size() == used.size()) { //结束标志
m_pun.push_back(tmp);
return;
}
for (int i = 0; i < used.size(); ++i) {
// 剪枝条件:i > 0 是为了保证 nums[i - 1] 有意义
// !used[i - 1] 是因为 nums[i - 1] 在深度优先遍历的过程中刚刚被撤销选择
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) //剪枝(除重)
continue;
if (!used[i]) {
used[i] = true;
tmp.push_back(nums[i]);
Do_PermuteUnique(tmp, used, nums); //回溯探索
used[i] = false;
tmp.pop_back();
}
}
}
//排列
vector<vector<int>> PermuteUnique(vector<int>& nums) {
m_pun.clear(); //0
if (nums.empty()) return m_pun; //0(空)
sort(nums.begin(), nums.end()); //排序使得重复字符相邻
vector<int> tmp; //tmp.size()为0
vector<bool> used(nums.size(), false);
Do_PermuteUnique(tmp, used, nums); //回溯
//Print_2D_Vector(m_res);
return m_pun;
}
//二维打印
template <typename T>
void Print_2D_Vector(T& list) {
for (int i = 0; i < list.size(); ++i) {
for (int j = 0; j < list[i].size(); ++j)
cout << list[i][j] << " ";
cout << endl;
}
}
//一维打印
void Print_1D_Vector(vector<int>& list) {
for (int i = 0; i < list.size(); ++i)
cout << list[i] << " ";
cout << endl;
}
//回溯,无重复组合
void Do_Combination(vector<int>& a, vector<int>& b, int l, int m, int M) {
//b用于临时存储结果。len(b)==M;l为左侧游标,初始值取0;M是取出个数;m用于指示深度,初始值取M)
vector<int> c;
int N = a.size();
if (m == 0) {
//for (auto i : b) {
// cout << i << ' ';
//}
//cout << endl;
//Print_1D_Vector(b);
c.clear();
for (int i : b) {
c.push_back(i);
}
sort(c.begin(), c.end()); //存在相同重复数个数的某组数排序后相同,如1,2,2和2,2,1
//cout << endl;
if (!count(m_com.begin(), m_com.end(), c)) { //跳过重复的组合方式
m_com.push_back(c);
}
return;
}
for (int i = l; i < N; ++i) {
b[M - m] = a[i];
Do_Combination(a, b, i + 1, m - 1, M);
}
}
//组合
vector<vector<int>> Combination(vector<int>& list, int& n) {
m_com.clear(); //0
if (list.empty()) return m_com; //0(空)
vector<int> a;
a.assign(list.begin(), list.end()); //把list放在a里
vector<int> b(n);
Do_Combination(a, b, 0, n, n);
//Print_2D_Vector(m_res);
return m_com;
}
//无重复的排列组合结果
vector<vector<int>> Permutation_Combination(vector<vector<int>>& list) {
m_con.clear();
vector<vector<int>> tmp;
//Print_2D_Vector(list);
for (int i = 0; i < list.size(); ++i) {
//Print_1D_Vector(list[i]);
tmp = PermuteUnique(list[i]);
//cout << "_________________" <<endl ;
m_con.insert(end(m_con), begin(tmp), end(tmp)); //将每次排序后得到的结果添加到尾部
}
//Print_2D_Vector(m_con);
return m_con;
}
};
main.cpp
#include<iostream>
#include<time.h>
#include"MyPerCom.h"
typedef vector<int> vec;
using namespace std;
vec Rand(int& m) { //生成有m个0~9随机数的数组
int a = 0; //a,b为随机区间
int b = 9;
vector<int> c;
srand(time(0));
//rand() % (b - a + 1) + a
for (int i = 0; i < m; ++i) {
c.push_back(rand() % (b - a + 1) + a);
}
return c;
}
int main() {
MyPerCom percom;
int n,m;
cout << "要进行排列组合的一组数的长度:";
cin >> n; //要进行排列组合的一组数的长度
cout<< "排列组合需要取数的个数:";
cin >> m ; //需要取数的个数
vec inpt = Rand(n); //随机产生要排列组合的一组数
//vec inpt{ 1,1,2,2,2 };
//int n = 3;
percom.Print_1D_Vector(inpt);
vector<vec> a,b;
if (m <= 0 || m > inpt.size() || inpt.empty()) {
cout <<"输入的请要求组合数字个数为0或者输入有误"<<endl;
return 0;
}
a = percom.Combination(inpt, m);
b = percom.Permutation_Combination(a); //排列组合
percom.Print_2D_Vector(b);
system("pause");
return 0;
}
测试效果