枚举法在我看来主要包括简单枚举、生成-测试法和回溯法
简单枚举
简单枚举:枚举一些相对简单的内容,基本不用动脑筋。
例题:
除法 (Uva 725)
最大乘积 (Uva 11059)
分数拆分(Uva 10976)
生成-测试:
生成-测试:将所有可能的解空间都生成出来,再一一测试是否符合条件
eg1 全排列:
现在有n个序列的缓存空间,先尝试填充第一个空间,而后再使用除第一个空间中的数字之外的数字填充第二个空间。依次类推知道填充完第n个空间。

void print_permutation(int n,int *A,int cur){
if(序列元素个数>=n){
依次输出序列P中元素
}
else{
for(int i = 1;i <= n;i ++){
if(i在1...cur-1序列中没有出现){
P[cur] = i;
print_permutation(n,P,cur + 1);//递归调用
}
}
}
}
eg2 可重集的全排列:
void print_permutation(int n,int *A,int cur){
if(序列元素个数>=n){
依次输出序列P中元素
}
else{
for(int i = 1;i <= n;i ++){
if(A[i]在P[0]...P[cur-1]序列中出现次数<A[i]在序列A中出现次数){
P[cur] = A[i];
print_permutation(n,P,cur + 1);
}
}
}
}
上面程序存在一个问题,输入1 1 1,会输出27个1 1 1。这是因为该程序会将第1个1作为开头,递归调用结束再使用第2个1作为开头,递归调用结束再使用第3个1作为开头。然而第1个1、第2个1、第3个1是相同的
因此我们在将该可重集排序之后,只需要检查第一个元素和每个重复的元素的第一个元素,因此我们需要在for循环{}前加上"if(!i || P[i] != p[i - 1])"
子集生成:
(位向量法):
设置一个位向量,其中向量中第m维坐标标识集合中的第m个元素(m取值范围1...n; 向量中m维度坐标取值为0,1。0标识这个元素不被选中,1标识这个元素被选中)
void print_subSet(int n,int *B,int cur){
if(cur>=n){
if(向量中第m维坐标标识为1) 输出集合中的第m个元素
}else{
for(int i = 1;i <= n;i ++){
B[cur] = 1;//表示选中集合中第m个元素
print_subSet(n,B,cur + 1);
B[cur] = 0;//表示不选中集合中第m个元素
print_subSet(n,B,cur + 1);
}
}
}
附:c++中"algorithm"头文件包含函数next_permutation();该函数提供可重集的全排列
可重集:包含重复元素的集合
回溯:
回溯法:将生成和测试过程结合起来,进而减少不必要的枚举。(如果在递归过程中发现其本身不符合条件,就返回上一层调用)
八皇后问题:
void search(int cur){
if(cur == n) tot ++;//递归边界
else{
for(int i = 0;i < n;i ++){
c[cur] = i;//尝试将第cur行的皇后放在第i列
if(当前和前面的皇后不冲突){
search(cur + 1);//继续递归
}
}
}
}
首先因为是逐行放置的,所以绝对不会产生横向攻击;接着如何判断是否产生纵向攻击,只需查看是否存在j行与c[cur]相同即可;关于如何判断是否产生斜向攻击可以通过主对角线和副对角线的规律可以得知
注:八皇后问题有一个思想很有趣:只判断当前皇后是否和前面的皇后冲突,但并不判断以前的皇后是否相互冲突
网友评论