当前位置 博文首页 > wohu1104的专栏:算法设计思想(1)— 穷举法

    wohu1104的专栏:算法设计思想(1)— 穷举法

    作者:[db:作者] 时间:2021-08-14 21:15

    本文系 王晓华 老师 GitChat 【算法应该怎么玩】课程笔记。

    1. 穷举法概念

    穷举法又称穷举搜索法,是一种在问题域的解空间中对所有可能的解穷举搜索,并根据条件选择最优解的方法的总称。

    数学上也把穷举法称为枚举法,就是在一个由有限个元素构成的集合中,把所有元素一一枚举研究的方法。

    穷举法一般用来找出符合条件的所有解,但是如果给出最优解的判断条件,穷举法也可以用于求解最优解问题。

    2. 设计思路

    使用穷举法解决问题,基本上就是以下两个步骤:

    • 确定问题的解(或状态)的定义、解空间的范围以及正确解的判定条件;
    • 根据解空间的特点来选择搜索策略,逐个检验解空间中的候选解是否正确;

    2.1 解空间定义

    解空间就是全部可能的候选解的一个约束范围,确定问题的解就在这个约束范围内,将搜索策略应用到这个约束范围就可以找到问题的解。

    2.2 穷举解空间的策略

    穷举解空间的策略就是搜索算法的设计策略,根据问题的类型,解空间的结构可能是线性表、集合、树或者图,对于不同类型的解空间,需要设计与之相适应的穷举搜索算法。

    如果选择一种搜索策略,不带任何假设的穷举搜索,不管行不行,眉毛胡子一把抓,把所有可能的解都检查一遍,这样的搜索通常被称为“盲目搜索”。

    与之对应的是利用某种策略或计算依据,由启发函数策动有目的的搜索行为,这些策略和依据通常能够加快算法的收敛速度,或者能够划定一个更小的、最有可能出现解的空间并在此空间上搜索,这样的搜索通常称为“启发性搜索”。

    一般来说,为了加快算法的求解,通常会在搜索算法的执行过程中辅助一些剪枝算法,排除一些明显不可能是正确解的检验过程,来提高穷举的效率。

    剪枝一个很形象的比喻,如果某一个状态节点确定不可能演化出结果,就应该停止从这个状态节点开始的搜索,相当于状态树上这一分枝就被剪掉了。

    除了采用剪枝策略,还可以使用限制搜索深度的方法加快算法的收敛,但是限制搜索深度会导致无解,或错过最优解,通常只在特定的情况下使用,比如博弈树的搜索。

    2.3 剪枝策略

    对解空间穷举搜索时,如果有一些状态节点可以根据问题提供的信息明确地被判定为不可能演化出最优解,也就是说,从此节点开始遍历得到的子树,可能存在正确的解,但是肯定不是最优解,就可以跳过此状态节点的遍历,这将极大地提高算法的执行效率,这就是剪枝策略,应用剪枝策略的难点在于如何找到一个评价方法(估值函数)对状态节点进行评估。

    3. 实例

    3.1 百钱买鸡问题

    一百个钱买一百只鸡,是个典型的穷举法应用。问题描述:每只大公鸡值 5 个钱,每只母鸡值 3 个钱,每 3 只小鸡值 1 个钱,现在有 100 个钱,想买 100 只鸡,问如何买?有多少种方法?

    1. 盲目搜索
      假设买 x 只公鸡,y 只母鸡,z 只小鸡,使用代码求解如下:
      %%time
      for x in range(1, 100):
          for y in range(1, 100):
              for z in range(1, 100):
                  if x + y + z == 100 and 5*x + 3*y + z/3.0 == 100:
                      print x, y, z
      
      输出结果
      4 18 78
      8 11 81
      12 4 84
      CPU times: user 76.3 ms, sys: 0 ns, total: 76.3 ms
      Wall time: 75.2 ms
      
    2. 启发搜索
      假设买 x 只公鸡,y 只母鸡,则 x 最大只能是 20 只,y 最大只能是 33 只,而小鸡则应该为 100 -x-y 只,使用代码求解如下:
      %%time
      for x in range(1, 21):
          for y in range(1, 34):
              if 5*x + 3*y + (100-x-y)/3.0 == 100:
                  print x, y, 100-x-y
      
      输出结果
      4 18 78
      8 11 81
      12 4 84
      CPU times: user 4.43 ms, sys: 0 ns, total: 4.43 ms
      Wall time: 2.56 ms
      

    可以看出第二种搜索算法比第一种明显快很多。

    3.2 鸡兔同笼问题

    穷举法的经典题目:鸡兔同笼问题。有鸡和兔在一个笼子中,数头共 50 个头,数脚共 120 只脚,问:鸡和兔分别有多少只?

    1. 盲目搜索
      假设买 x 鸡,y 只兔,使用代码求解如下:
      %%time
      for x in range(1, 51):
          for y in range(1, 51):
              if x + y == 50 and 2*x + 4*y == 120:
                  print x, y
      
      输出结果
      40 10
      CPU times: user 0 ns, sys: 3.19 ms, total: 3.19 ms
      Wall time: 2.17 ms
      
    2. 启发搜索
      假设买 x 鸡,则兔子数量只能是 50 - x ,使用代码求解如下:
      %%time
      for x in range(1, 51):
          if 2*x + 4*(50-x) == 120:
              print x, 50-x
      
      输出结果
      40 10
      CPU times: user 190 μs, sys: 0 ns, total: 190 μs
      Wall time: 137 μs
      

    同样也可以看出第二种搜索算法比第一种明显快很多。

    cs