2020-11-01

95 篇文章 2 订阅

原来程序可以这样写!——八数码问题优选算法(重磅)

前期对八数码问题进行过三次探讨,见以下链接:
八数码问题
八数码问题(续)
八数码问题(再续)
这三次探讨都没有涉及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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值