全组合算法(Full Combination)
1072 字
5 分钟
全组合算法(Full Combination)
定义
从含 个元素的集合 中任取 ()个元素,组成的子集称为组合(Combination)。当 时,组合数为
给定集合 和 时,所有可能的组合构成的集合称为全组合(Full Combination)。全组合的实现同样可分递归(回溯)与非递归。下文默认集合元素为 (与代码一致);若要使用 ,初始化时改为 a[i] = i + 1 即可。
递归实现
1. 递归回溯法
由组合恒等式出发
可把从 个元素中取 个元素拆解成一系列与单一元素 相关的两个子问题:
- 不选 :在剩余 个中取 个 ;
- 选 :在剩余 个中再取 个 。
递归边界:已取满 个则输出;下标越界且未取满则失败返回。实现上是回溯:先试探选中当前元素,再试探不选;对应递归调用中,先递归调用不选,再递归调用选。
#include <iostream>#include <vector>using namespace std;
vector<int> res;int N, M;int *a;
void print() { for (int i = 0; i < (int)res.size(); ++i) { cout << res[i] << ' '; } cout << endl;}
// k:当前考虑的下标;m:还需要再取多少个(此处用 res.size() 与 M 判断亦可)void combination(int k, int m) { if ((int)res.size() == M) { print(); return; } if (k >= N) { return; } res.push_back(a[k]); combination(k + 1, m - 1); // 选 res.pop_back(); combination(k + 1, m); // 不选}
int main() { cin >> N >> M; a = new int[N]; for (int i = 0; i < N; ++i) { a[i] = i; } combination(0, M); delete[] a; return 0;}2. 顺序法(递归)
更常见的写法:强制按递增下标选取,避免同一子集因顺序不同被重复枚举。先取第 个元素可选范围为下标 ,再递归取后续;当还剩 个名额时,遍历剩余元素并输出。
#include <iostream>#include <vector>using namespace std;
vector<int> res;int N, M;int *a;
void print() { for (int i = 0; i < (int)res.size(); ++i) { cout << res[i] << ' '; } cout << endl;}
void combination(int k, int m) { if (m == 1) { for (int i = k; i < N; ++i) { res.push_back(a[i]); print(); res.pop_back(); } return; } for (int i = k; i <= N - m; ++i) { res.push_back(a[i]); combination(i + 1, m - 1); res.pop_back(); }}
int main() { cin >> N >> M; a = new int[N]; for (int i = 0; i < N; ++i) { a[i] = i; } combination(0, M); delete[] a; return 0;}非递归实现
3. 顺序法(嵌套循环)
用 层循环,每层选一个严格递增的下标。与全排列里的蛮力法一样:层数绑死 ,改 就要改代码。
以 为例:
#include <iostream>#include <vector>using namespace std;
vector<int> res;int N;int M = 3;int *a;
void print() { for (int i = 0; i < (int)res.size(); ++i) { cout << res[i] << ' '; } cout << endl;}
int main() { cin >> N; a = new int[N]; for (int i = 0; i < N; ++i) { a[i] = i; } for (int i1 = 0; i1 < N - 2; ++i1) { res.push_back(a[i1]); for (int i2 = i1 + 1; i2 < N - 1; ++i2) { res.push_back(a[i2]); for (int i3 = i2 + 1; i3 < N; ++i3) { res.push_back(a[i3]); print(); res.pop_back(); } res.pop_back(); } res.pop_back(); } delete[] a; return 0;}4. 状态序列 + 全排列
对于每个元素 ,只有在与不在子集中两种状态。可构造长度为 、恰含 个 1 与 个 0 的状态串 , 的每一种不同排列对应一种组合。那么问题就转换为了求 的互异全排列问题。
例:,,初始 ,用字典序/反字典序枚举 的互异排列:
| 状态 | 取出的元素 |
|---|---|
全排列算法见 全排列一文。非递归时可用 prev_permutation / next_permutation 枚举状态串:
#include <iostream>#include <algorithm>#include <vector>#include <string>using namespace std;
vector<int> res;int N, M;int *a;string b;
void print() { for (int i = 0; i < (int)res.size(); ++i) { cout << res[i] << ' '; } cout << endl;}
int main() { cin >> N >> M; a = new int[N]; b.append(M, '1').append(N - M, '0'); for (int i = 0; i < N; ++i) { a[i] = i; } do { for (int i = 0; i < N; ++i) { if (b[i] == '1') { res.push_back(a[i]); } } print(); res.clear(); } while (prev_permutation(b.begin(), b.end())); delete[] a; return 0;}提示
next_permutation / prev_permutation 在有重复元素时只会生成互异排列,因此对 个 、 个 的串,枚举次数是 而非 。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
全组合算法(Full Combination)
https://blog.scxs-studio.com/posts/full-combination/相关文章智能推荐
1
全排列算法(Full Permutation)
学习笔记总结全排列算法的定义与若干实现(交换法、递归生成树、穷举、字典序、SJT 算法)。
2
第三代博客:Astro + Firefly 的迁移与改造记录
建站从 WordPress、Hexo 迁移到 Astro 框架的踩坑记录。
3
碧蓝航线大型作战 - 指挥喵物资搜寻
游戏攻略对碧蓝航线大型作战玩法中的「指挥喵物资搜寻」的游戏机制进行详细探究与数据统计。
4
Hessian-based Post-Training Quantization
论文阅读论文阅读笔记:基于 Hessian 矩阵的后训练量化算法系列论文:AdaRound(ICML 2020)、BRECQ(ICLR 2021)、QDrop(ICLR 2022)。
5
How to Break MD5 and Other Hash Functions
论文阅读论文阅读笔记:曾经轰动世界的著名论文 How to Break MD5 and Other Hash Functions(EUROCRYPT 2005)。
随机文章随机推荐


