人工智能课程设计——八皇后问题的求解算法比较

最近暑假有些空闲时间,于是把这学期写的一些课程设计记录下来,如果有需要请自取,原创不易,希望能给各位的学习带来一些方便。

八皇后问题的求解算法及步骤分析

回溯法——通用的解法

回溯法被称为一种通用的解题法,它的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。这种方法适用于解一些组合数相当大的问题。回溯法在问题的解空间树中,是按深度优先策略,从根结点出发去搜索解空间树。当算法搜索至解空间树的任意一点时,首先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其根结点进行回溯;否则,进入该子树,继续按深度优先策略搜索。其核心思想就是走不通则调头,类似于走迷宫 。

回溯法求解八皇后问题具体步骤为:

八个皇后用 q = 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 q=0,1,2,3,4,5,6,7 q=0,1,2,3,4,5,6,7来表示,其中第一个皇后放在8*8 矩阵的 ( 0 , 0 ) (0,0) (0,0)位置,也就是 q = 0 , x [ q ] = 0 q=0,x[q]=0 q=0,x[q]=0 ,这里的 q表示行和皇后q, 表示列。
⑴、在第一行放下第一个皇后之后,再放下一个皇后时是从第二行开始放,此时需要判断皇后放置在第二行的哪一列;
⑵、如果下一个皇后摆放位置与其他皇后位置冲突(即皇后在同一列或同一斜线上),就进行 x [ q ] + + x[q]++ x[q]++ ,一直找到不发生冲突的位置;
⑶、一直进行判断是否有剩余的皇后或是否发生越界,假如还有剩余的皇后,则进行 ,继续摆放下一个皇后,假如有皇后发生越界,则进行回溯,即 x [ q ] = − 1 , q = q − 1 x[q]=-1,q=q-1 x[q]=1,q=q1
检查皇后 是否发生冲突代码:

int check(int q){
	for(int i=0;i<q;i++){
		if(x[i]==x[q] || abs(x[i]-x[q])== abs(i-q))//检查皇后摆放位置是否在同一列或同一斜线
			return 1;
		return 0;
 }

关于回溯法的相关代码是用 语言实现的 C + + C++ C++,输出结果为不发生冲突的八皇后各皇后的位置,一共输出有 92 92 92 种摆法。

爬山法——经典的解法

爬山法思想虽然简单但是有效,不断去选择最短路径达到目标,将损耗尽可能减小到最低,故被人称为“贪婪算法”。于是算法的核心就是一个简单的循环,不断向值增加的方向持续移动——登高。直到算法到达一个“峰顶”时才终止,这时邻接状态中没有比它的值更高的。
对于爬山法这样的算法人们将它归类于局部搜索算法。有些搜索算法是需要返回从初始状态到目标状态的这条路径作为这个问题的解;而实际中有很多最优化的问题,是不需要知道到达目标的这条路径。它只关心状态本身,只需要算法找到一个符合当前要求的目标状态,那么这个目标状态本身才是问题的解。针对这一类问题我们就可以采用局部搜索算法。
局部算法只需要考虑当前状态与邻接状态,不需考虑全局的状态信息,自然计算量就小很多。但是,它也有一个致命的缺陷——当达到局部极大值时就会卡在这里无路可走,因为在一个局部极大值点的周围邻接点与之相比都不是最优,不管移动到哪一个都只会比原来的更差[5]。

接下来详细分析一下爬山法求解八皇后问题的具体步骤:
⑴、输入八皇后的初始位置信息,计算该位置出现冲突的八皇后数目作为损失最小值;
⑵、按照初始化八皇后位置变化顺序,依次输入变化位置信息,计算该组位置的损失并与上一损失比较,保留损失最小的八皇后位置信息;
⑶、不断循环进行比较,直到找到损失为零的八皇后位置信息,即保证各皇后不会发生冲突的位置信息。

比较八皇后位置信息代码:

best = []
next = self.getNeighbor(self.puzzle)
min = self.getEvaluation(self.puzzle)
if min == 0:
   return -1
for state in next:
   now = self.getEvaluation(state)
   if now <= min:
      min = now
      best = state
if len(best) != 0:
   self.puzzle = best

关于爬山法的相关代码是用python语言实现的,其中函数self,getNeighbor 是得到八皇后相邻位置的变化信息,函数self,getEvaluation是计算八皇后位置冲突的损失。
虽然发展到今天出现了许多优秀的算法来处理各类搜索问题,但直接而又快速的爬山法不失是一种非常经典的算法。

随机重启爬山法——前进的解法

如果说爬山算法是一种经典的算法,那么随机重启爬山法可谓是爬山法 2.0 2.0 2.0,它是为了克服爬山法中无法逃出局部极大值而进化得到的,最大的不同是如果当前的状态,没有达到最优值,即冲突为零的八皇后位置,就不断改变状态,重新开始搜索,直到找到不会发生冲突的八皇后位置。通过随机生成的初始状态来引导爬山法进行搜索,直到找到目标,这样找到目标的成功率会大很多。
随机重启爬山法的步骤代码:

while True:
    totalCosts += 1
    test = self.climbHill()
    if last == self.puzzle:
       if test == -1:
          break
       else:
          self.new_state()
       last = self.puzzle
       if totalCosts > 40000:
          return

这是随机重启爬山法的一个死循环代码片段,只有当八皇后位置信息没有冲突,即test==-1时,代码在会跳出循环,否则不断输入初始随机位置状态信息。其中当循环次数 totalCosts>4000,默认本轮搜索失败,跳出算法程序。

模拟退火法——他山之石

模拟退火法也是一种局部搜索算法,它是由 K i r k p a t r i c k Kirkpatrick Kirkpatrick等人 ( 1983 ) (1983) (1983)首次描述。退火是冶金学中的概念,它是指用于增强金属和玻璃的韧性或硬度而先把它们加热至高温再让它们逐渐冷却的过程,这样能使材料到达低能量的结晶态。模拟退火的解决方法就是开始使劲晃动(高温加热)随后慢慢降低晃动的强度(逐渐降温)。
模拟退火算法的内层循环与爬山法类似,只是它没有选择最佳移动,而是随机移动。如果该移动使情况改善,该移动则被接收。否则,算法以某个小于 1 1 1的概率接收该移动。如果移动导致状态“变坏”,概率则成指数级下降——评估值 Δ E \Delta E ΔE变坏。这个概率也随“温度” 降低而下降:开始 T T T高的时候可能允许“坏的”移动, T T T越低则越不可能发生。如果调度让 T T T下降得足够慢,算法找到最优解的概率逼近于 1 1 1
模拟退火法的思路是如果移动后能够得到更优的解,那么新的解就成为当前的解,但如果移动后的解比当前解更差,则它则会以一定概率 P P P成为当前的解[2]。 P P P的求解公式为:
p = e − ( n e x t C o s t − n o w ) / T &ThinSpace; . p=e^{-(nextCost-now)}/T\,. p=e(nextCostnow)/T.

其中 n e x t C o s t nextCost nextCost为随机移动后状态解, n o w now now为当前的状态解。这个公式这说明算法会在开始阶段更可能的接受表现较差的解,随着退火过程的不断进行, T T T 不断减小,算法越来越不可能接受较差的解,直到最后,它只会接受更优的解,随着T不断减小, n e x t C o s t nextCost nextCost n o w now now 之间的差异也越来越重要,差异越大,被接受的概率也越低。
模拟退火法实现的步骤代码:

while True:
    T += 0.1
    next = self.getNeighbor(self.puzzle)
    now = self.getEvaluation(self.puzzle)
    if now == 0:
break
    ra = random.randint(0, len(next) - 1)
    choose = next[ra]
    nextCost = self.getEvaluation(choose)
    deltaE = nextCost - now
    if nextCost == 0:
       self.puzzle = choose
       break
    if deltaE < 0:
       self.puzzle = choose
       totalCosts += 1
    else:
       bound = math.exp(-deltaE / T)
       compare = random.random()
if compare >= bound:
         self.puzzle = choose
         totalCosts += 1
    if T > 10000:
       return

函数 s e l f . g e t N e i g h b o r self.getNeighbor self.getNeighbor 是得到八皇后相邻位置的变化信息,函数 s e l f . g e t E v a l u a t i o n self.getEvaluation self.getEvaluation 是计算八皇后位置冲突的损失, d e l t a E deltaE deltaE 为随机得到状态的损失与当前状态的损失的差值, b o u n d bound bound 则为概率 P P P 。同样当循环次数 t o t a l C o s t s &gt; 10000 totalCosts &gt; 10000 totalCosts>10000 ,默认本轮搜索失败,跳出算法程序。

A* 算法——启发式解法

A ∗ A* A算法是一个运用广泛的算法,非常适用于目标搜索,路径优化等问题。它对节点的评估结合了 g ( n ) g(n) g(n) h ( n ) h(n) h(n) ,即到达此节点已经花费的代价和从该节点到目标节点所花费的代价:
f ( n ) = g ( n ) + h ( n ) f(n)=g(n)+h(n) f(n)=g(n)+h(n)
由于 g ( n ) g(n) g(n)是从开始节点到节点n的路径代价,而 h ( n ) h(n) h(n) 是从节点n到目标结点的最小代价路径的估计值,因此

f ( n ) = f(n)= f(n)= 经过结点 的最小代价解的估计代价

我们只需要考虑如何找到从该节点到目标结点所花费的代价 h ( n ) h(n) h(n)的最小值,也就意味着达到目标代价 f ( n ) f(n) f(n)最小。而 h ( n ) h(n) h(n)取值也正对应启发式问题的有效性,对一个好的 h ( n ) h(n) h(n)的评价是: h ( n ) h(n) h(n)是在节点n 到目标结点实际代价的下界之下,并且尽量接近真实代价。
接下来详细分析一下 A ∗ A* A算法求解八皇后问题的具体步骤:
⑴、首先建立一个队列pQ和四个列表open 、openValue、close 和cloeValue ,分别装八皇后位置状态和其对于位置状态的损失值。其中队列pQ内第一列元素为位置状态损失值,第二列为位置状态信息;
⑵、取出pQ队列中第二列元素,如果该元素不在close 中,则将该行元素分别输入至列表close 和cloeValue ;
⑶、循环查询各皇后位置状态对应的损失值,如果皇后位置状态节点同时不在close 和open 列表内,则将节点对应的皇后位置状态和其损失值赋值给open和openValue,并将此节点赋值给队列pQ;如果皇后位置状态节点不在close 并且在open 内,则判断该节点对应的损失值在列表中的大小,并将最小损失值和其对应的皇后位置赋值给队列 pQ;如果皇后位置状态节点在close 内,则同样判断损失值大小,并将最小损失值和其对应的皇后位置从close 和cloeValue 列表中删除,将它们赋值给open和openValue以及队列pQ。
⑷、如果节点可扩展,将节点的扩展节点加入到open和openValue表中,将open和openValue表按照估价函数由小到大排列;否则跳转第⑵步。
A ∗ A* A算法实现的步骤代码:

while not pQ.empty():
    element = pQ.get()
    totalCosts += 1
    if element[1] not in close:
       close.append(element[1])
       closeValue.append(element[0])
    for i in self.getNeighbor(element[1]):
       if i not in close and i not in open:
          evaluate = self.getEvaluation(i)
          open.append(i)
          openValue.append(evaluate)
          pQ.put((evaluate, i))
elif i not in close and i in open:
      evaluate = self.getEvaluation(i)
      if evaluate < openValue[open.index(i)]:
openValue[open.index(i)] = evaluate
        pQ.put((evaluate, i))
    elif i in close:
       evaluate = self.getEvaluation(i)
       if evaluate < closeValue[close.index(i)]:
closeValue.remove(closeValue[close.index(i)])
close.remove(i)
open.append(i)
openValue.append(evaluate)
pQ.put((evaluate, i))
       if totalCosts > 400:
          return
if element[0] == 0:
           self.puzzle = element[1]
           break

以上是 A ∗ A* A算法的实现过程,和它的思想一样,由到达此结点已经花费的代价和从该节点到目标结点所花费的代价组成,在选取该节点到目标结点所花费的代价时为了达到最优性,建立两个列表选取代价最小的作为估计代价。

完整代码请在我的资源处下载

参考文献

1 Sprague T B . On the Eight Queens Problem[J]. Proceedings of the Edinburgh Mathematical Society, 2009, 17:26.
2 Gruenberger F J . OPTIMIZING THE EIGHT QUEENS OVERLAY PROBLEM,[J]. Rand Corporation, 1965.
3 Ioannidis Y E , Wong E . [ACM Press the 1987 ACM SIGMOD international conference - San Francisco, California, United States (1987.05.27-1987.05.29)] Proceedings of the 1987 ACM SIGMOD international conference on Management of data, - SIGMOD "87 - Query optimization by simulated annealing[J]. 1987:9-22.
4 Bussey W H . A Note on the Problem of the Eight Queens[J]. Acta Medica Scandinavica, 1940, 103(3-5):251–258.
5 Yuen C K , Feng M D . Breadth-first search in the Eight Queens Problem[J]. ACM SIGPLAN Notices, 1994, 29(9):51-55.
6 Stuart Russell. 人工智能——一种现代方法 : 第2版[M]// 人工智能:一种现代方法.第2版. 2010.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值