枚举法简介
枚举法(Enumeration),也称为穷举法或暴力搜索法,是一种基本的算法设计策略。它通过系统地遍历所有可能的解,从中找出满足条件的解。
枚举法的核心思想
枚举法的核心思想是"穷尽所有可能性"——通过遍历问题的所有可能解,然后检查每个解是否满足问题的条件,从而找到问题的解。
枚举法是最直观、最容易理解和实现的算法之一。在C++中,枚举法通常通过循环结构实现。
C++枚举法的特点:使用for/while循环、if条件判断、数组/容器等基本语法,易于理解和实现。
C++枚举法的基本概念
定义:枚举法是一种通过遍历所有可能情况来寻找问题解的算法策略。在C++中,通常使用嵌套循环来实现多维枚举。
枚举法的三个要素(C++视角)
C++枚举法的基本结构
典型的C++枚举法代码结构:
#include <iostream>
using namespace std;
int main() {
// 1. 确定解空间范围
for (int i = start; i <= end; i++) {
for (int j = start2; j <= end2; j++) {
// 2. 计算或获取当前解
// 3. 检查是否满足条件
if (condition(i, j)) {
// 4. 输出或记录解
cout << "解: " << i << ", " << j << endl;
}
}
}
return 0;
}
C++枚举法示例
示例1:百钱买百鸡问题(C++实现)
问题描述:公鸡5文钱一只,母鸡3文钱一只,小鸡1文钱三只。用100文钱买100只鸡,问公鸡、母鸡、小鸡各多少只?
C++实现代码:
#include <iostream>
using namespace std;
int main() {
int count = 0; // 记录解的个数
// 枚举公鸡数量(0-20只,因为5*20=100)
for (int cock = 0; cock <= 20; cock++) {
// 枚举母鸡数量(0-33只,因为3*33≈100)
for (int hen = 0; hen <= 33; hen++) {
// 小鸡数量 = 总数 - 公鸡 - 母鸡
int chick = 100 - cock - hen;
// 检查条件:钱数正好100文,且小鸡数量是3的倍数
if (chick % 3 == 0 && cock * 5 + hen * 3 + chick / 3 == 100) {
count++;
cout << "解" << count << ": 公鸡" << cock << "只, "
<< "母鸡" << hen << "只, "
<< "小鸡" << chick << "只" << endl;
}
}
}
cout << "共有" << count << "种买法" << endl;
return 0;
}
时间复杂度分析:外层循环21次,内层循环34次,总共21×34=714次循环,O(n²)级别。
示例2:找出水仙花数(C++实现)
问题描述:水仙花数是指一个3位数,其各位数字的立方和等于该数本身。例如:153 = 1³ + 5³ + 3³。
C++实现代码:
#include <iostream>
#include <cmath> // 使用pow函数
using namespace std;
int main() {
cout << "三位数中的水仙花数有:" << endl;
int count = 0;
// 枚举所有3位数(100-999)
for (int num = 100; num <= 999; num++) {
// 分解各位数字
int hundreds = num / 100; // 百位
int tens = (num / 10) % 10; // 十位
int units = num % 10; // 个位
// 计算各位数字的立方和
int sum = pow(hundreds, 3) + pow(tens, 3) + pow(units, 3);
// 判断是否为水仙花数
if (sum == num) {
count++;
cout << "第" << count << "个: " << num
<< " = " << hundreds << "³ + "
<< tens << "³ + " << units << "³" << endl;
}
}
cout << "共有" << count << "个水仙花数" << endl;
return 0;
}
时间复杂度分析:循环900次,O(n)级别,非常高效。
示例3:简单密码破解(C++实现)
问题描述:已知一个3位数的密码锁,每位数字为0-9,尝试所有可能的组合来解锁。
C++实现代码:
#include <iostream>
#include <string>
using namespace std;
int main() {
string password = "123"; // 假设的正确密码
bool found = false;
int attempts = 0;
cout << "开始破解3位数密码..." << endl;
// 枚举所有3位数组合(000-999)
for (int i = 0; i <= 9; i++) {
for (int j = 0; j <= 9; j++) {
for (int k = 0; k <= 9; k++) {
attempts++;
// 构造当前尝试的密码字符串
string attempt = "";
attempt += to_string(i);
attempt += to_string(j);
attempt += to_string(k);
// 检查是否匹配
if (attempt == password) {
found = true;
cout << "密码破解成功!" << endl;
cout << "密码是: " << attempt << endl;
cout << "尝试次数: " << attempts << endl;
return 0; // 找到后立即退出
}
}
}
}
if (!found) {
cout << "未找到密码!" << endl;
}
return 0;
}
时间复杂度分析:三重循环,每层10次,总共10×10×10=1000次循环,O(n³)级别。
C++枚举法的实现模式
1. 简单循环枚举
适用于解空间是一维的情况,如遍历一个范围内的所有整数。
// 找出1-1000中所有能被3和5整除的数
#include <iostream>
using namespace std;
int main() {
cout << "1-1000中能被3和5整除的数:" << endl;
for (int i = 1; i <= 1000; i++) {
if (i % 3 == 0 && i % 5 == 0) {
cout << i << " ";
}
}
cout << endl;
return 0;
}
2. 多重循环枚举
适用于解空间是多维的情况,如组合问题、排列问题。
// 找出所有三位数ABC,满足ABC = A! + B! + C!
#include <iostream>
using namespace std;
// 计算阶乘的辅助函数
int factorial(int n) {
if (n == 0 || n == 1) return 1;
int result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
int main() {
cout << "三位数中满足ABC = A! + B! + C! 的数:" << endl;
for (int a = 0; a <= 9; a++) {
for (int b = 0; b <= 9; b++) {
for (int c = 0; c <= 9; c++) {
int num = 100 * a + 10 * b + c;
int sumFact = factorial(a) + factorial(b) + factorial(c);
if (num == sumFact && num >= 100) { // 确保是三位数
cout << num << " = " << a << "! + "
<< b << "! + " << c << "!" << endl;
}
}
}
}
return 0;
}
3. 递归枚举
适用于解空间规模不确定或需要生成所有排列/组合的情况。
// 生成长度为n的所有二进制串
#include <iostream>
#include <string>
using namespace std;
void generateBinaryStrings(int n, string current) {
// 基础情况:达到指定长度
if (current.length() == n) {
cout << current << endl;
return;
}
// 递归生成:当前位可以是0或1
generateBinaryStrings(n, current + "0");
generateBinaryStrings(n, current + "1");
}
int main() {
int n = 3; // 生成3位二进制串
cout << "所有" << n << "位二进制串:" << endl;
generateBinaryStrings(n, "");
return 0;
}
4. 状态压缩枚举
使用二进制位来表示状态,适用于子集选择问题。
// 枚举集合{1,2,3}的所有子集
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> nums = {1, 2, 3};
int n = nums.size();
cout << "集合 {1,2,3} 的所有子集:" << endl;
// 枚举所有子集(0到2^n-1)
for (int mask = 0; mask < (1 << n); mask++) {
cout << "{";
bool first = true;
// 检查mask的每一位
for (int i = 0; i < n; i++) {
// 如果第i位为1,则包含元素nums[i]
if (mask & (1 << i)) {
if (!first) cout << ", ";
cout << nums[i];
first = false;
}
}
cout << "}" << endl;
}
return 0;
}
C++枚举法的优化技巧
C++优化原则:减少循环次数、避免重复计算、使用位运算、利用STL算法
1. 减少循环范围
示例:百钱买百鸡问题的优化
// 原始版本:x:0-20, y:0-33
// 优化版本:减少不必要的循环
#include <iostream>
using namespace std;
int main() {
int count = 0;
// 优化1:当公鸡数量增加时,母鸡的最大数量减少
for (int cock = 0; cock <= 20; cock++) {
// 优化2:根据剩余钱数计算母鸡的最大数量
int maxHen = (100 - cock * 5) / 3;
if (maxHen > 33) maxHen = 33;
for (int hen = 0; hen <= maxHen; hen++) {
int chick = 100 - cock - hen;
// 优化3:提前检查小鸡数量是否为3的倍数
if (chick % 3 != 0) continue;
if (cock * 5 + hen * 3 + chick / 3 == 100) {
count++;
cout << "公鸡" << cock << ", 母鸡" << hen << ", 小鸡" << chick << endl;
}
}
}
cout << "共有" << count << "种买法" << endl;
return 0;
}
优化效果:循环次数从714次减少到约300次
2. 预处理和记忆化
示例:计算多次查询的阶乘
// 预处理阶乘表,避免重复计算
#include <iostream>
#include <vector>
using namespace std;
int main() {
const int MAX_N = 10;
vector<long long> fact(MAX_N + 1);
// 预处理:计算0-10的阶乘
fact[0] = 1;
for (int i = 1; i <= MAX_N; i++) {
fact[i] = fact[i-1] * i;
}
// 测试查询
int queries[] = {0, 1, 5, 7, 10};
int nQueries = sizeof(queries) / sizeof(queries[0]);
for (int i = 0; i < nQueries; i++) {
int n = queries[i];
cout << n << "! = " << fact[n] << endl;
}
return 0;
}
优化效果:查询时间复杂度从O(n)降到O(1)
3. 使用位运算优化
示例:判断整数奇偶性
// 使用位运算判断奇偶性,比取模运算更快
#include <iostream>
using namespace std;
int main() {
int numbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int n = sizeof(numbers) / sizeof(numbers[0]);
cout << "使用位运算判断奇偶性:" << endl;
for (int i = 0; i < n; i++) {
int num = numbers[i];
// 方法1:使用取模运算(较慢)
bool isEven1 = (num % 2 == 0);
// 方法2:使用位运算(更快)
bool isEven2 = ((num & 1) == 0);
cout << num << ": " << (isEven2 ? "偶数" : "奇数") << endl;
}
return 0;
}
优化效果:位运算通常比算术运算更快
C++枚举法的时间复杂度分析
C++中常见枚举问题的时间复杂度
| 问题类型 |
C++循环结构 |
时间复杂度 |
是否可行 |
| 3位密码锁 |
三重for循环 |
O(10³) = O(1000) |
✓ 可行 |
| 百钱买百鸡 |
双重for循环 |
O(20×33) = O(660) |
✓ 可行 |
| 水仙花数 |
单重for循环 |
O(900) |
✓ 可行 |
| 6位密码锁 |
六重for循环 |
O(10⁶) |
✓ 可行(较慢) |
| 15个元素的子集 |
位运算枚举 |
O(2¹⁵) = O(32768) |
✓ 可行 |
| 20个元素的子集 |
位运算枚举 |
O(2²⁰) = O(1,048,576) |
✓ 可行 |
| 25个元素的子集 |
位运算枚举 |
O(2²⁵) = O(33,554,432) |
△ 勉强可行 |
| 8位密码锁 |
八重for循环 |
O(10⁸) |
△ 勉强可行 |
C++性能提示:现代C++编译器会进行循环优化,但嵌套循环超过3层时性能会显著下降。对于大规模枚举,应考虑算法优化或使用并行计算。
C++枚举法在线编译器
在下面编写和运行C++枚举法代码:
运行结果将显示在这里