原来程序可以这样写!——八数码问题优选算法(重磅)
前期对八数码问题进行过三次探讨,见以下链接:
八数码问题
八数码问题(续)
八数码问题(再续)
这三次探讨都没有涉及A
∗
^*
∗算法,所以效率比较低。对于高难度案例,尚不能在短时间内获得结果,这是我一直耿耿于怀的。
A
∗
^*
∗(A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法,也是解决许多搜索问题的有效算法。算法中的距离估算值与实际值越接近,最终搜索速度越快。
看了网上的一些A*算法案例,受到一些启发。但疑问总是有的。
其一,A
∗
^*
∗算法是否一定能给出最少移动步数?
其二,最高难度的案例最少须要花费多少步?
有一个网友宣称,八数码问题如果有解,最多32步完成。我一开始是持怀疑态度的。
因为我看到一些A
∗
^*
∗算法在解决123405678到876504321时花费了72步,经验证确实如此,就不附带链接了。
现在我认为,高效解决八数码问题,关键在曼哈顿距离的应用。
曼哈顿距离是这样描述的:若平面上两个点的坐标为(x1,y1)、(X2,y2),则两个点的曼哈顿距离为:abs(x1-x2)+abs(y1-y2)。
我是这样想的:
1、如果八数码问题有解,那么,在众多的次生状态中,一定有一种(或多种)状态是符合最短路径要求的。
2、上述描述的这种(或多种)状态,一定能够通过曼哈顿距离的计算筛选后获得。筛选的方法是,对于某个层级的多种状态,保留那些距目标状态较近的状态(曼哈顿距离越短,离目标状态越近),我把那些被筛选出来的状态称之为种子(seed)。
3、关键来了,每次筛选,保留种子的数量不超过500。
这个数据是通过反复验证得到的,是一个折中的数据,如果往小设置,可能会得不到解或不是最优解,如果往大设置,运算的时间会延长。
当验证出从123405678到876504321只须要30步时,我惊呆了。我不知道这是否就是最短路径,因为我在网上搜到一个C++写的程序,运行后竟然是28步(是否把头尾去掉了?),但没有显示具体的路径,并且感觉这个程序有些问题。望各位指教!
最复杂的案例到底是什么?最少花费多少步?对我来说,还是一个谜。
程序清单:
class Lists: # 建立链表类,按层次保存状态列表
def __init__(self, layer, father):
self.layer = layer
self.sun = moving(father)
self.father = father
def moving(state): # 统计从一个状态(father)变化到下一个状态(sun)的所有列表
sun = []
zero_ind = state.index(0)
for b in base[zero_ind][1]:
temp = copy.deepcopy(state)
temp[b], temp[zero_ind] = 0, temp[b]
sun.append(temp)
return sun
# 判断是否有解
def solution(state):
_count = 0
for _i in range(9):
if _i != 0:
for _j in range(_i):
if state[_j] != 0 and state[_i] > state[_j]:
_count += 1
return _count
# 代价计算,计算当前状态与目标状态间的曼哈顿距离,是优选种子的关键
def manhattan(state):
_count = 0
for _i in range(9):
for _j in range(9):
if state[_i] == target[_j]:
_count += abs(_j % 3 - _i % 3) + abs(_j // 3 - _i // 3)
return _count
# 显示从起始到目标所有状态变化过程
def show():
global target
for _i in range(count, 0, -1): # 逆向搜根
for _s in sum_list:
if target in _s.sun and _s.layer == _i:
result.append(_s.father)
target = _s.father
break
result.reverse()
for _r in result:
for _i in range(9):
print(_r[_i], end=" ")
if (_i + 1) % 3 == 0:
print()
print()
print("耗时{}s。".format(time.time() - t0))
if __name__ == '__main__':
import time
import copy
# source = [2, 8, 3, 1, 0, 4, 7, 6, 5]
# target = [2, 8, 3, 7, 6, 0, 1, 4, 5]
# source = [1, 8, 7, 3, 0, 5, 4, 6, 2]
# target = [1, 2, 3, 4, 5, 6, 7, 8, 0]
# source = [1, 2, 3, 4, 5, 6, 7, 8, 0]
# target = [0, 8, 7, 6, 5, 4, 3, 2, 1]
source = [1, 2, 3, 4, 0, 5, 6, 7, 8]
target = [8, 7, 6, 5, 0, 4, 3, 2, 1]
# 记录空格(0)从一个状态(位置)变化到下一个状态(位置)信息
base = [[0, [1, 3]], [1, [0, 2, 4]], [2, [1, 5]], [3, [0, 4, 6]], [4, [1, 3, 5, 7]],
[5, [2, 4, 8]], [6, [3, 7]], [7, [4, 6, 8]], [8, [5, 7]]]
sum_list = [] # 存放记录状态链表数据
t0 = time.time()
c1, c2 = solution(source), solution(target)
if (c1 - c2) % 2 != 0:
print("无解!")
exit()
seed = [source]
count = 1
result = []
while True:
for f in seed:
L = Lists(count, f) # 实例化链表类
for t in L.sun:
if t == target:
print("成功!共花费{}步。".format(count))
sum_list.append(L)
result.append(target)
show()
exit()
sum_list.append(L)
# 去重,关键环节
for s in sum_list:
if s.layer == count - 1 and s.father in L.sun:
L.sun.remove(s.father)
# 优选种子:对新一轮产生的所有状态按曼哈顿距离进行评估,选取前500个种子
seed = []
for i in sum_list:
if i.layer == count:
for ii in i.sun:
seed.append(ii)
# 按曼哈顿距离排序,距离越短,排序越靠前;排序500以后的舍去
seed.sort(key=lambda k: manhattan(k))
while len(seed) > 500:
del seed[-1]
count += 1
if count > 100:
print("搜索层次超过设定值,失败!")
exit()
D:\Python\Python38\python.exe D:/Python/study/eight3.py
成功!共花费30步。
1 2 3
4 0 5
6 7 8
1 0 3
4 2 5
6 7 8
0 1 3
4 2 5
6 7 8
4 1 3
0 2 5
6 7 8
4 1 3
6 2 5
0 7 8
4 1 3
6 2 5
7 0 8
4 1 3
6 2 5
7 8 0
4 1 3
6 2 0
7 8 5
4 1 0
6 2 3
7 8 5
4 0 1
6 2 3
7 8 5
0 4 1
6 2 3
7 8 5
6 4 1
0 2 3
7 8 5
6 4 1
7 2 3
0 8 5
6 4 1
7 2 3
8 0 5
6 4 1
7 2 3
8 5 0
6 4 1
7 2 0
8 5 3
6 4 0
7 2 1
8 5 3
6 0 4
7 2 1
8 5 3
0 6 4
7 2 1
8 5 3
7 6 4
0 2 1
8 5 3
7 6 4
8 2 1
0 5 3
7 6 4
8 2 1
5 0 3
7 6 4
8 2 1
5 3 0
7 6 4
8 2 0
5 3 1
7 6 0
8 2 4
5 3 1
7 0 6
8 2 4
5 3 1
0 7 6
8 2 4
5 3 1
8 7 6
0 2 4
5 3 1
8 7 6
5 2 4
0 3 1
8 7 6
5 2 4
3 0 1
8 7 6
5 0 4
3 2 1
耗时10.149173498153687s。
Process finished with exit code 0