文章内容来自王晓华老师
穷举法又称穷举搜索法,是一种在问题域的解空间中对所有可能的解穷举搜索,并根据条件选择最优解的方法的总称。数学上也把穷举法称为枚举法,就是在一个由有限个元素构成的集合中,把所有元素一一枚举研究的方法。
使用穷举法解决问题,基本上就是以下两个步骤:
• 确定问题的解(或状态)的定义、解空间的范围以及正确解的判定条件;
• 根据解空间的特点来选择搜索策略,逐个检验解空间中的候选解是否正确;
解空间的定义
解空间就是全部可能的候选解的一个约束范围,确定问题的解就在这个约束范围内,将搜索策略应用到这个约束范围就可以找到问题的解。要确定解空间,首先要定义问题的解并建立解的数据模型。如果解的数据模型选择错误或不合适,则会导致解空间结构繁杂、范围难以界定,甚至无法设计穷举算法。
穷举解空间的策略
穷举解空间的策略就是搜索算法的设计策略,根据问题的类型,解空间的结构可能是线性表、集合、树或者图,对于不同类型的解空间,需要设计与之相适应的穷举搜索算法。简单的问题可以用通用的搜索算法,比如线性搜索算法用于对线性解空间的搜索,广度优先和深度优先的递归搜索算法适用于树型解空间或更复杂的图型解空间。
盲目搜索和启发式搜索
对于线性问题的盲目搜索,就是把线性表中的所有算法按照一定的顺序遍历一遍,对于复杂问题的盲目搜索,常用广度优先搜索和深度优先搜索这两种盲目搜索算法。
如果搜索能够智能化一点,利用搜索过程中出现的额外信息直接跳过一些状态,避免盲目的、机械式的搜索,就可以加快搜索算法的收敛,这就是启发性搜索。启发性搜索需要一些额外信息和操作来“启发”搜索算法,根据这些信息的不同,启发的方式也不同。
剪枝策略
对解空间穷举搜索时,如果有一些状态节点可以根据问题提供的信息明确地被判定为不可能演化出最优解,也就是说,从此节点开始遍历得到的子树,可能存在正确的解,但是肯定不是最优解,就可以跳过此状态节点的遍历,这将极大地提高算法的执行效率,这就是剪枝策略,应用剪枝策略的难点在于如何找到一个评价方法(估值函数)对状态节点进行评估。特定的评价方法都附着在特定的搜索算法中,比如博弈树算法中常用的极大极小值算法和“α-β”算法,都伴随着相应的剪枝算法。
剪枝和启发
剪枝不是启发性搜索。剪枝的原理是在结果已经搜索出来或部分搜索出来(比如树的根节点已经搜索出来了,但是叶子节点还没有搜索出来)的情况下,根据最优解的判断条件,确定这个方向上不可能存在最优解,从而放弃对这个方向的继续搜索。而启发性搜索通常是根据启发函数给出的评估值,在结果出来之前就朝着最可能出现最优解的方向搜索。它们的差异点在于是根据结果进行判断还是根据启发函数的评估值进行判断。
搜索算法的评估和收敛
收敛原则是只要能找到一个比较好的解就返回(不求最好),根据解的评估判断是否需要继续下一次搜索。大型棋类游戏通常面临这种问题,比如国际象棋和围棋的求解算法,想要搜索整个解空间得到最优解目前是不可能的,所以此类搜索算法通常都通过一个搜索深度参数来控制搜索算法的收敛,当搜索到指定的深度时(相当于走了若干步棋)就返回当前已经找到的最好的结果,这种退而求其次的策略也是不得已而为之
百钱买鸡问题
一百个钱买一百只鸡,是个典型的穷举法应用。问题描述:每只大公鸡值 5 个钱,每只母鸡值 3 个钱,每 3 只小鸡值 1 个钱,现在有 100 个钱,想买 100 只鸡,问如何买?有多少种方法?
void Buy() { int count = 0; for (int roosters = 0; roosters <= 20; roosters++) //枚举大公鸡数量 { for (int hens = 0; hens <= 33; hens++) //枚举母鸡数量 { int chicks = 100 - roosters - hens; //剩下的就是小鸡数量 if (((chicks % 3) == 0) //小鸡个数应该是 3 的整数倍,算是个小小的剪枝 && ((5 * roosters + 3 * hens + chicks / 3) == 100)) //是否凑够 100 钱 { count++; std::cout << "买法 " << count << ":公鸡 " << roosters << ", 母鸡 " << hens << ", 小鸡 " << chicks << std::endl; } } } std::cout << "共有 " << count << " 种买法" << std::endl; }
-- lua实现 function bug() local count = 0 for roosters = 0, 20 do for hens = 0, 33 do local chicks = 100 - roosters - hens if (((chicks % 3) == 0) and ((5 * roosters + 3 * hens + chicks / 3) == 100)) then count = count + 1 print("====买法", count, roosters, hens, chicks) end end end print("====共有买法", count) end
鸡兔同笼问题
有鸡和兔在一个笼子中,数头共 50 个头,数脚共 120 只脚,问:鸡和兔分别有多少只?
for ji = 1, 50 do local tu = 50 - ji if (tu * 4 + ji * 2) == 120 then print("===========鸡 兔各", ji, tu) break end end