上一年的人工智能课就已经把八数码的BFS DFS A* 遗传算法都试了一遍.昨天上传旧的时候觉得实现的不是很优雅,现在想重新用python来一遍.这次我实现了一个很大的突破,起码比原来的算法实现的速度提高了几十倍~
题目如图,首先我们先建立一个棋盘类来进行八数码问题的操作.
class board:
def __init__(self):
# self.groud = [1,0,2,3,4,5,6,7,8]
# 棋盘,0代表空
self.groud = [7, 2, 4, 5, 0, 6, 8, 3, 1]
#移动的路径
self.route = []
# 是否到达正确状态
def guiwei(self):
flag = True
for i in range(9):
if self.groud[i] != i:
flag = False
break
return flag
# 两个位置数字进行交换
def exchange(self, index1, index2):
temp = self.groud[index1]
self.groud[index1] = self.groud[index2]
self.groud[index2] = temp
# 空格向左移动
def left(self):
index = self.groud.index(0)
if index % 3 != 0:
self.exchange(index, index-1)
# 空格向右移动
def right(self):
index = self.groud.index(0)
if index % 3 != 2:
self.exchange(index, index+1)
# 空格向上移动
def up(self):
index = self.groud.index(0)
if int(index/3) != 0:
self.exchange(index, index-3)
# 空格向下移动
def down(self):
index = self.groud.index(0)
if int(index/3) != 2:
self.exchange(index, index+3)
# 返回空格能移动的方位的列表,比如[0,1,2,3]代表空格能向左右上下进行移动
def can_move(self):
index = self.groud.index(0)
can = []
if index % 3 != 0:
can.append(0)
if index % 3 != 2:
can.append(1)
if int(index/3) != 0:
can.append(2)
if int(index/3) != 2:
can.append(3)
return can
# 展示棋盘
def show_board(self):
print(self.groud)
# 路径
def show_route(self):
print(self.route)
print(len(self.route))
# 通过route路径进行移动,route是路径列表
def move(self, route):
for i in route:
if i == 0:
self.left()
elif i == 1:
self.right()
elif i == 2:
self.up()
else:
self.down()
# 仅移动一步
def move_one(self, i):
if i == 0:
self.left()
elif i == 1:
self.right()
elif i == 2:
self.up()
else:
self.down()
# 测试如果向目标方向移动,棋盘的变化,返回变化的棋盘
def test(self, i):
groud = []
if i == 0:
self.left()
groud = self.groud[:]
self.right()
elif i == 1:
self.right()
groud = self.groud[:]
self.left()
elif i == 2:
self.up()
groud = self.groud[:]
self.down()
else:
self.down()
groud = self.groud[:]
self.up()
return groud
然后我先写出来了一个没有任何优化的极简BFS
from board import board
def BFS_waste_time():
deq = []
b = board()
for x in b.can_move():
deq.append([x])
flag = 0
while True:
flag+=1
temp = deq.pop(0)
if flag%10000==0:
flag=0
print(temp)
print(len(temp))
b_temp = board()
b_temp.move(temp)
if b_temp.guiwei():
print(temp)
break
for x in b_temp.can_move():
new_temp=temp[:]
new_temp.append(x)
deq.append(new_temp)
很简单通过队列,先进后出就能实现.这个算法是没有问题的,实现也没有问题,就是这么个暴力计算的话需要算很久才能算出来.接下来这个是经过我优化的.
import copy
def list2str(li):
s = ""
for x in li:
s+=str(x)
return s
def BFS():
deq = []
appear = set()#使用set进行检索极快,hash检索
temp = board()
appear.add(list2str(temp.groud))
for x in temp.can_move():
b = board()
b.move_one(x)
b.route.append(x)
appear.add(list2str(b.groud))
deq.append(b)
flag = 0
while True:
flag+=1
temp = deq.pop(0)
if flag%10000==0:
flag=0
temp.show_board()
temp.show_route()
if temp.guiwei():
temp.show_board()
temp.show_route()
break
for x in temp.can_move():
# 筛选掉重复的棋盘情况,加快搜索速度
if list2str(temp.test(x)) not in appear:
new_temp=copy.deepcopy(temp)
new_temp.move_one(x)
new_temp.route.append(x)
deq.append(new_temp)
appear.add(list2str(new_temp.groud))
提高的速度比简单的BFS提升速度少说几千倍~比优化过的JAVA版快了几十倍.优化重点:
- 重复的棋盘就不要入队了.在移动过程中大概率会出现这种情况,移动了几步回到了原来的棋盘的情况.我们将出现过的棋盘情况全部记录下来放在appear,移动后如果是新棋盘就入队,如果不是就不再入队进行搜索.
- 由于每次都需要进行appear的检索,如果单纯地使用路径列表进行检索的话很慢很慢.我搜索了一下知道一件事情,set()集合的搜索是可以使用哈希值进行搜索的,复杂度为O(1),所以,将路径转化为字符串(字符串才是可哈希的),然后使用字符串进行检索,速度直接起飞~
其它尝试了的优化,这些优化可能影响不大
- 尝试过使用cython提高效率,但是不是很明显
- test函数的出现是因为觉得复制可能比较耗时,所以就使用test函数进行.
代码 | 时间 |
JAVA没注意哈希检索版 | 658.932 s |
python哈希优化版 | 13.831229 s |
说明:JAVA的路径是指0的位置(设计的不是很优雅,前面两个4就是0一开始就在4的位置) python的路径是移动的方向
总结:在BFS中尝试了很多方法进行速度优化,从算法层面来讲,剪枝应该就是BFS中比较好用的优化方法了,后面的哈希表以及cython个人觉得属于代码方面的优化了.不过哈希也是算法,第一次切身体会到数据结构上的优化带来的巨大提升.