回溯法遵循深度优先吗_回溯法/深度优先遍历的简单优化技巧

深度优先遍历配合回溯,是解决很多问题的好方法,比如八皇后问题。

皇后的排布规则:n个皇后放在n*n的矩阵里,要求一列只有一个,一行只有一个,任一斜线上只有一个(/和\)。

通常,我们会把皇后作为一个数组,行号作为数组的下标,而列号是数组元素的值,由此,二维平面的排布问题就成了一维数组的求解,配合检验函数以及回溯,就可以求解了。

这里,我使用一个状态表(一个好的状态表可以比检验函数要有效率的多)来维护某个行可能的填入皇后的列号,每次放下皇后或者拿起皇后,都会对状态表进行更新。按行号的顺序依次安置皇后,安放完最后一个皇后,就得到了问题的一个解。

但是这会带来一个问题,那就是求解的难度会随着皇后的数量增加,耗时会剧增,差不多是O(n^3)的程度。

所以,我们要找一个可以更快解决问题的方法。

注意,八皇后问题的一个隐含的条件是:我们可以在任意位置安放皇后,没有次序的要求。但是当我们抽象到二维数组的时候,往往会忽略这一点。现在,考虑到这个情况,我们有如下解决方案:

当我们放下一个皇后之后,可以从剩余的所有行中,选择一个候选列号最少的,进行下一步的安置。

这样做的好处时,可以大大减弱搜索树的规模,这种减弱越靠近根部,就越明显。

因此,我们可以加入一个交换的函数。当然,这样一来,我们的状态表里就应该加上关于行号和列号的记录了。

最终代码如下:

num=8

class Queen(object):

def __init__(self,n):

self.lct= n

self.prs=-1

self.cdt=[1 for i in range(num)]

def Count(q):

s=0

for i in range(num):

if q.cdt[i]>0:s+=1

return s

def FindIt(q):

u=q.prs+1;

while u

if u

q.prs=u

return True

return False

def Settle(q,n):

x=q[n].lct

y=q[n].prs

for i in range(n+1,num,1):

p=q[i].cdt

p[y]-=1

a=q[i].lct-x

b=y-a

if b>=0 and b

b=y+a

if b>=0 and b

def Pickup(q,n):

x=q[n].lct

y=q[n].prs

for i in range(n+1,num,1):

p=q[i].cdt

p[y]+=1

a=q[i].lct-x

b=y-a

if b>=0 and b

b=y+a

if b>=0 and b

def Select(q,n):

j,k=0,num+1

for i in range(n,num):

t=Count(q[i])

if k>t:j,k=i,t

if j!=n:q[n],q[j]=q[j],q[n]

def ShowIt(q):

for i in range(num):

for j in range(num):

if q[j].lct==i:

for k in range(num):

if q[j].prs==k:

print '*',

else:

print '-',

print ''

print ''

def Locate1():

q=[Queen(i) for i in range(num)]

i=0

j=0

while 1:

if q[i].prs<0:

Select(q,i)

else:

Pickup(q,i)

if FindIt(q[i]):

if i

Settle(q,i)

i+=1

else:

j+=1

yield j

#ShowIt(q)

else:

q[i].prs=-1

i-=1

if i<0:break

def Locate2():

q=[Queen(i) for i in range(num)]

i=0

j=0

while 1:

if q[i].prs>=0:Pickup(q,i)

if FindIt(q[i]):

if i

Settle(q,i)

i+=1

else:

j+=1

yield j

#ShowIt(q)

else:

q[i].prs=-1

i-=1

if i<0:break

if __name__=='__main__':

q=[Queen(i) for i in range(num)]

import time

t=time.time()

Locate1().next()

print 'once cost %.6f'%(time.time()-t)

print '-----------------'

t=time.time()

Locate2().next()

print 'once cost %.6f'%(time.time()-t) 算法很简单,也没有注释。我是用一个列表同时保存了皇后的行号、列号和状态表。当皇后放下后,状态表表示她放下时的所有可能位置(也就是放下后就不更新了),未放下的皇后的状态表会不断更新。一个指针,该指针左边都是放下的,右边都是未放下的,指针所指的皇后元素,是要进行挪动的那个。

那么,结果呢?如下:

(08) 92 , 0.027 <=> 0.024 , ----- <=> ------

(09) 352 , 0.113 <=> 0.096 , ----- <=> ------

(10) 724 , 0.467 <=> 0.432 , ----- <=> ------

(11) 2680 , 1.919 <=> 1.956 , ----- <=> ------

(12) 14200 , 9.786 <=> 10.647 , ----- <=> ------

(13) 73712 , 52.080 <=> 59.131 , ----- <=> ------

(14) 365596 , 326.946 <=> 462.586 , ----- <=> ------

(15) ------ , ------- <=> ------- , ----- <=> ------

(16) ------ , ------- <=> ------- , 0.002 <=> 0.175

(17) ------ , ------- <=> ------- , 0.002 <=> 0.103

(18) ------ , ------- <=> ------- , 0.003 <=> 0.770

(19) ------ , ------- <=> ------- , 0.002 <=> 0.053

(20) ------ , ------- <=> ------- , 0.005 <=> 4.075

(21) ------ , ------- <=> ------- , 0.004 <=> 0.195

(22) ------ , ------- <=> ------- , 0.002 <=> 36.618

(23) ------ , ------- <=> ------- , 0.003 <=> 0.563

(24) ------ , ------- <=> ------- , 0.003 <=> 9.280

(25) ------ , ------- <=> ------- , 0.013 <=> 1.157

(26) ------ , ------- <=> ------- , 0.022 <=> 9.391

(27) ------ , ------- <=> ------- , 0.008 <=> 11.390

(28) ------ , ------- <=> ------- , 0.003 <=> 74.833

(29) ------ , ------- <=> ------- , 0.029 <=> 42.061

(30) ------ , ------- <=> ------- , 0.018 <=> ------

(40) ------ , ------- <=> ------- , 0.158 <=> ------

(50) ------ , ------- <=> ------- , 0.040 <=> ------

(60) ------ , ------- <=> ------- , 0.032 <=> ------

(70) ------ , ------- <=> ------- , 0.077 <=> ------

(80) ------ , ------- <=> ------- , 0.054 <=> ------

(90) ------ , ------- <=> ------- , 2.162 <=> ------

上表的最左边是皇后的个数;第一栏是解的数量;随后的一对数据依次是优化和非优化版本的求全部解的耗时;最后一对数据依次是优化和非优化版本的求第一个解的耗时。

可见这种优化的效果还是很好的。(虽然小数量是反而慢一些,但这是必然的,算法越精巧,也就要做越多的处理操作,自然会在处理小规模数据时不利)

这种改进,对于没有次序要求的深度搜索/回溯求解非常有效,比如,数独。

本质上,这种方法和A*算法有些异曲同工,一个是旨在“剪枝”,一个则使用经验函数“抄近路”,而它们的使用上,其实都是一样的:对所有可能的下一步进行排序之后,选择效果最好/几率最高的进行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值