C++算法封装:穷举法


  将算法独立抽象出来,在C++中算不上新鲜:STL中就封装了不少高效、健壮、灵活的泛型组件及对应的基础算法,工艺之高、适用性之强,非寻常我辈所轻易能及。这里不打算(也暂没有能力打算)以STL这样的工业级要求来谈论算法封装,只因最近尝翻大师名著,阅者水平有限,仅嗅触至皮毛,理智薄弱,感情却蓬勃发展:也欲尝试“封装”的味道。选择了最简易的穷举算法,抽其骨架,炮制成class,套上一实际例子,观之run之,抽象程度颇低,效率损失弥彰;然却也自觉有可爱之处,遂作此文以记之。诚惶诚恐,便于名目之前加“闲谈”二字,倘果因技术问题招致痛骂,则试以此二字为护文符,聊且一挡。
  众所周知,穷举法可视为最简单的搜索:即是在一个可能存在可行状态(可行解)的状态全集中依次遍历所有的元素,并判断是否为可行状态。例如,要设计一个“从一堆苹果中找出蓝色的苹果”这样的穷举算法,则定义:
  (1) 状态全集:一堆苹果
  (2) 可行状态:蓝色的苹果
  噢,好,我们现在已经抽取了两个基本概念,迫不及待要开始穷举了,但……怎么做呢?穷举的关键是“依次遍历”,即做到不重、不漏。呃,我们可以让听话的苹果们排成一行,放在“苹果数组”中,然后呢,我们就可以按照0号苹果、1号苹果、2号苹果、...、n号苹果这样的顺序成功遍历。好,我们解决了遍历苹果的问题……慢,我们现在是设计一个算法的抽象模型,如果一切待穷举的对象都已经活生生地摆在那里,当然有可能把它们全部收集起来并排队,但如果开始的时候我们并不知道所有要穷举的对象,比如我们或许要通过一台安装在探测飞船内的计算机在全宇宙范围内穷举出除地球以外有生命的星球,那么我们的计算机可能是随着飞船的前行方能不断地得到新星球的信息,而不是停在地球的时候就获得全宇宙的星球信息(就算可能,内存或许也装不下如此大的数据量——哪怕宇宙真的是有限“大”的)。所以我们不应当要求穷举进行之前就能获得状态全集中的所有状态,这样一来,我们的“苹果数组”计划就宣告流产了。现在再看看我们激动人心的星球搜索计划:它并没有把所有星球收罗排队,那么它成功的关键在哪里?在于飞船能否以适当的路径“光顾”完所有的星球;我们把这个条件加强一下:飞船每次到达一个星球,都会看到星球上立着一个方向标,标示下一个星球的方位;而假定这样的标示保证飞船能够不重不漏地飞临宇宙中的所有星球。啊喔……你是不是觉得我这是在异想天开?哦,没关系,大不了我们不搜索星球了,而除此之外的很多现实穷举问题都可以满足这个加强条件。嗯,我承认本文讨论的是满足这个加强条件的稍稍狭义的穷举:它必须保证在知道一个状态的前提下能获得一个新状态,并且这样的“状态链”刚好能遍历整个状态全集。我们称从当前状态获得并转到下一个状态的过程为“状态跳转”(我是想用“状态转移”的,嗨,可惜它会与动态规划算法的术语相混淆):    (3)状态跳转:根据当前得到的苹果,按一定的“遍历算法”取得下一个苹果;这个算法保证不重不漏地取遍苹果堆中的所有苹果,只要所取的第一个苹果也是按算法定义给出的
  很显然,对于不同的穷举任务,都会有不同的遍历算法,所以这样一来我们就得将其实现下放给调用我们“穷举算法库”的用户们了。不过考虑到这的确是由于问题的多样性所决定的,因而这个要求应当是合理的。
  嗯啊,现在我们已经有了苹果源,目标苹果,乃至遍历苹果的方案(用户提供),接下来还差一个判断标准,这个倒简单了:
  (4) 判断标准:当前苹果是否为蓝色的苹果
  下一步,我们就可以考虑“the classof穷举算法”的具体实现了。我们把这个class的名字定义为WalkThrough.
  首先,让我们注意到,“状态”是一个很重要的概念:不同的穷举问题都有彼此不同的状态,在苹果问题中,“状态”是苹果,它包含了苹果颜色或者更多的信息;而在星球搜索计划中,“状态”则是星球,它可能包含该星球的体积、平均密度、温度、是否有大气、是否探测到水、星表活动状况等一系列丰富得惊人的信息。因此,不同状态(state)对应不同的数据类型,要让WalkThrough能处理它们,有必要使用模板,于是我们的最初定义如下:
  template
  class WalkThrough
  ;
  万事开头难,但现在我们显然已经开了一个不错的头,嗯,继续。在考虑具体实现之前,先幻想一下我们的WalkThrough能为用户提供怎样的服务——当然,它的本职工作是找到并返回可行状态,因此它似乎应该有一个类似于getFilter()的成员函数。问题是,如果可行状态不止一个时,getFilter()应当返回一个可行状态还是所有的可行状态?不难想象,返回所有可行状态的作法并不太现实,因为:1.有时候用户只需要一个,或者少数几个可行状态,此时把所有的可行状态都穷举出来显然是低效而不必要的;2.甚至,有些问题的可行状态数量是无限的,如穷举素数,此时返回所有状态当然不可能。同时考虑到用户要求的仍有可能是不止一个可行解,我们现在知道,应当提供一个getNextFilter()而不是getFilter()的函数:第一次调用它时,将返回从初始状态开始,依序遍历到的第一个可行状态;而此后的调用都将以上次调用为起点继续向前遍历,返回下一个可行状态。需要注意的是,如果已经遍历完了状态全集,显然再调用此函数是没有意义的,所以它应当返回一个标志,反馈给用户是否遍历已经完成。我们将这个函数定义为bool,如果调用有效,则返回true,反之如果已经完成遍历,则返回false.显然,我们相应需要一个私有的State对象变量curState,它用于存储当前的状态值。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值