A*算法求解实验

目录

实验目的

实验要求

实验过程 (分析+流程图+代码+运行结果)

八数码问题

5*5迷宫寻路问题

 总结


实验目的

熟悉和掌握启发式搜索的定义、估价函数和算法过程

实验要求

熟练掌握A*算法的基本原理。分析不同启发式函数对问题求解的提升效果。

  1. 实现A*算法的求解,要求设计两种不同的估价函数:设置相同的初始状态和目标状态,针对不同估价函数,求得问题的届,比较它们对搜索算法性能的影响,包括扩展节点数、生成节点数、运行时间等。
  2. 画出流程图。
  3. 源程序代码

实验过程 (分析+流程图+代码+运行结果)

  • 八数码问题

  1. 不同的估价函数对搜索算法性能的影响

对于同一问题启发函数h(n)可以有多种设计方法。在本次时实验中,通过选用“将牌不在位数”和“将牌‘不在位’的距离和”两种不同的启发函数,同时还编写了不考虑h值进行搜索,即不采用启发性搜索的算法(按照广度优先搜索的策略)。,我们通过将第一种和第二种启发函数对比,发现第二种启发函数优于第一种启发函数,将采用启发函数与不采用启发性函数对比发现,采用启发性函数远远优于不采用启发性函数。第二种启发函数为h(n)=将牌‘不在位’的距离和,初始时的值为6,将牌1:2,将牌2:1,将牌6:2,将牌7:1,将牌8:2。对于不同启发函数对于实验结果和实验效率的影响较为明显。第三种启发函数是按照广度优先搜索的策略。

2.流程图

3.源程序代码

class Astar:

    def salvePuzzle(self, init, targ):
        # 传入初始状态init,目标状态targ,返回移动路径(string形式)
        open = [(init, self.calcDistH(init, targ))]
        # open表元素(状态,f值)
        closed = {}
        dict_depth = {init: 0}
        # 深度表,用来记录节点是否被访问过及对应的深度
        dict_link = {}
        # 关系表,用来记录父子关系,孩子-->父亲
        dict_dirs = {'u': [-1, 0], 'd': [1, 0], 'l': [0, -1], 'r': [0, 1]}
        # 移动方向,对应二维坐标的变化
        dirs = ['l', 'r', 'u', 'd']
        path = []
        # 用来记录移动路径
        while (len(open)):
            open.sort(key=lambda open: open[1])
            # 每循环一次对列表按由小到大排序一次
            while (open[0][0] in closed):
                open.pop([0])
            # 如果表头元素在closed表中,移出open表
            if (open[0][0] == targ):
                print("Successfully!")
                break
            top = open[0]
            open[0:1] = []
            closed[top[0]] = top[1]
            # 取表头元素,并将表头元素移出open表,压入closed表
            cur_index = top[0].index('0')
            for i in range(4):
                x = cur_index // 3 + dict_dirs[dirs[i]][0]
                y = cur_index % 3 + dict_dirs[dirs[i]][1]
                if (x >= 0 and x < 3 and y >= 0 and y < 3):
                    next_index = x * 3 + y
                    next_state = self.moveMap(top[0], cur_index, next_index)
                    depth = dict_depth[top[0]] + 1
                    # 当前节点不在深度表中,压入深度表和open表,并建立父子关系
                    if ((next_state in dict_depth) == False):
                        dict_depth[next_state] = depth
                        open.append(
                            (next_state, depth + self.calcDistH(next_state, targ)))
                        dict_link[next_state] = top[0]
                    else:
                        # 当前节点在深度表中且当前深度小于深度表中的值,更新深度表,建立父子关系
                        if (depth < dict_depth[next_state]):
                            dict_depth[next_state] = depth
                            dict_link[next_state] = top[0]
                            # 如果当前节点在closed表中,将其移出closed表,将更新后的节点移入open表
                            if (next_state in closed):
                                del closed[next_state]
                                open.append(next_state, depth +
                                            self.calcDistH(next_state, targ))
        # 循环结束,路径关系全部在dict_link中,从目标状态出发寻找路径
        s = targ
        while (s != init):
            move = s.index('0') - dict_link[s].index('0')
            if (move == -1):
                path.append('l ')
            elif (move == 1):
                path.append('r ')
            elif (move == -3):
                path.append('u ')
            else:
                path.append('d ')
            s = dict_link[s]
        path.reverse()
        # 将path逆序(如果想要打印出路径每一步的状态,只需要按照path和init就能实现)
        print("SearchPath->", "".join(path))
        return "".join(path)

    def calcDistH(self, src_map, dest_map):
        # 采用曼哈顿距离作为估值函数
        cost = 0
        for i in range(len(src_map)):
            if (src_map[i] != '0'):
                cost += abs(int(src_map[i]) // 3 - i // 3) + \
                        abs(int(src_map[i]) % 3 - i % 3)
        return cost

    def moveMap(self, cur_map, i, j):
        cur_state = [cur_map[i] for i in range(9)]
        cur_state[i] = cur_state[j]
        cur_state[j] = '0'
        return "".join(cur_state)


# 本程序实现了Astar类,可通过创建Astar对象来调用相关方法
# 以下仅为测试用例
test = Astar()
test.salvePuzzle("724506831", "012345678")

运行结果

 

  • 5*5迷宫寻路问题

  1. 不同的估价函数对搜索算法性能的影响

估价函数:从当前节点移动到目标节点的预估费用;这个估计就是启发式的。在寻路问题和迷宫问题中,我们通常用曼哈顿(manhattan)估价函数预估费用。选取最小估价:对于每次都要选取最小估价的节点,应该用到最小优先级队列(也叫最小二叉堆)。在C++STL里有现成的数据结构priority_queue,可以直接使用。当然不要忘了重载自定义节点的比较操作符。

2.流程图

 

3.源程序代码

import numpy as np
import time
a = np.mat([[0, 0, 0, 0, 0],
            [1, 0, 1, 0, 1],
            [0, 0, 1, 1, 1],
            [0, 1, 0, 0, 0],
            [0, 0, 0, 1, 0]])

def gs(i, j):
    return abs(i - startx) + abs(j - starty)
def h1(i, j):
    return 10*(abs(i - endx) + abs(j - endy))
def h2(i, j):
    return pow(i - endx, 2) + pow(j - endy, 2)
print("地图为:(1表示墙,0表示路)")
for l in range(len(a)):
    for m in range(a[0].size):
        print(a[l,m], end=' ')
    print('')
print('')

startx, starty = 1, 1
endx, endy = 5, 5
if a[startx - 1, starty - 1] == 1:
    print("起点%s位置为墙,最短路径寻找失败!" % ([startx, starty]))
else:
    # 存储已扩展结点,即所有寻找的节点
    Close = [[startx, starty]]
    # 存储已生成节点,即最终走过的节点
    Opens = [[startx, starty]]
    # 存储未走的分叉节点
    crossings = []
    # 设置在原地图行走的路径值
    road = 100
    start = time.time()
    rows, cols = a.shape
    while True:
        # 判断最后走过的节点是否为终点
        if Close[-1] != [endx, endy]:
            Open = []
            # 减1得到数组下标值
            i, j = Close[-1][0] - 1, Close[-1][1] - 1
            # 对当前节点上下左右四个节点进行判断
            for ni, nj in [(i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)]:
                if [ni + 1, nj + 1] not in Opens and 0 <= ni < rows and 0 <= nj < cols and a[ni, nj] == 0:
                    Open.append([ni + 1, nj + 1])
            # 将已走过的节点值修改为路径值,并将路径值加1
            a[i, j] = road
            road = road + 1
            if Open:
                # 对所有扩展到的节点进行排序,reverse=True 结果从大到小,即尾部存储代价最小的节点
                Open = sorted(Open, key=lambda x: gs(x[0], x[1]) + h2(x[0], x[1]), reverse=True)
                Opens.extend(Open)
                # 将代价最小的节点存储到 Close 表
                Close.append(Open.pop())
                # 如果 pop 后 Open 表不为空,说明存在岔路,将岔路存储到 crossings 中
                if Open:
                    crossings.extend(Open)
            # 判断是否存在未走过的分叉节点
            elif crossings:
                next_way = crossings.pop()
                # 实现路径回退,循环条件为回退节点与分叉节点不相邻
                while sum((np.array(Close[-1]) - np.array(next_way)) ** 2) != 1:
                    # 当一条路径寻找失败后,是否将该路径岔路后的部分恢复为原地图
                    # i, j = Close[-1]
                    # a[i-1, j-1] = 0
                    # 每次 while 循环路径值 road 减1,并弹出 Close 表中的节点
                    road -= 1
                    Close.pop()
                # 将 crossings 中最后一个节点添加到 Close 表
                Close.append(next_way)
            else:
                print("最短路径寻找失败,失败位置为:%s,路径为:" % Close[-1])
                break
        else:
            a[endx - 1, endy - 1] = road
            print("最短路径寻找成功,路径为:")
            break
    for r in range(rows):
        for c in range(cols):
            # 0表示format中第一个元素,>表示右对齐输出,4表示占四个字符
            print("{0: >4}".format(a[r, c]), end='')
        print('')
    end = time.time()
    print("\n扩展节点数为:%d, 生成节点数为:%d, 用时为 %.8f" % (len(Opens), len(Close), float(end - start)))

    print('Close表为:%s' % Close)
    print("点的移动轨迹为:")
    for k in range(len(Close)):
        print(Close[k], end='')
        if k < len(Close) - 1:
            print("-->", end='')

 运行结果

 总结

        通过本次实验,发现选用不同的启发函数,对于实验的结果有较大的影响。选用第一或第二种(也就是采用A*算法)远远优于普通的广度优先搜索,同时,明显的感觉到第二种启发函数效率更高,更快的找到最优解。但是,在实验过程中,也遇到了一些问题,比如初始值的八数码初始值的选择对于实验结果的影响很大,在选取一些样例时,比如1,3,0,2,8,4,7,6,5,实验结果达到20000次依然没有停止,无法比较两种启发函数的优越性,鉴于时间原因,选取一些迭代次数较小就可以达到目标状态的样例进行验证,发现第二种结果优于第一种启发函数的结果。A*算法在地图寻路问题中具有很好的优势,相比宽度优先搜索,效率更高,所耗时间更少,相比深度优先搜索,可以解决其不能找到最优解的不足,具有较高的应用价值。该算法的重点及难点就是启发式函数的选择,通过上述实验,可以发现启发式函数h2相比h1效果更好,但仍存在一些问题,需要继续找寻更优的启发式函数。总的来说,实践出真知,只有把书上的理论知识运用到实践,才是真正地掌握。  

        A算法是一种有效的搜索算法,用于解决路径查找和图遍历问题。它结合了最佳优先搜索的高效性和Dijkstra算法的准确性,通过评估函数来预测从当前节点到目标节点的最佳路径。A算法的评估函数通常表示为:f(n) = g(n) + h(n),其中g(n)是从起始点到当前节点n的实际成本,而h(n)是当前节点n到目标节点的估计成本(启发式)。在解决八数码问题和55迷宫寻路问题时,A算法的表现通常取决于启发式函数h(n)的选择。八数码问题是一个经典的滑动拼图问题,目标是通过滑动数字来从初始状态移动到目标状态。而5*5迷宫寻路问题则需要在迷宫中找到从起点到终点的最短路径。

对以上做一个归纳:

  1. 启发式函数的选择:对于八数码问题,常见的启发式函数包括曼哈顿距离、欧几里得距离和不在位的数码数量等。对于迷宫寻路问题,通常使用曼哈顿距离作为启发式函数。合适的启发式函数能够显著提高搜索效率。

  2. 性能分析:A*算法的性能很大程度上取决于启发式函数的准确性。一个好的启发式函数可以减少搜索空间,从而加快搜索速度。在实验中,我们可以比较不同启发式函数对解决问题所需时间和空间的影响。

  3. 优化与改进:在实验过程中可能会发现A*算法在某些情况下效率不高,这可能是由于启发式函数过于乐观或者过于悲观。在这种情况下,可以尝试调整启发式函数,或者引入其他技术,如迭代加深、双向搜索等,以提高算法的效率。

  4. 数据结构的选用:A*算法中通常使用优先队列来存储待探索的节点,这样可以保证每次都能从队列中取出具有最小f(n)值的节点进行扩展。合理的数据结构可以减少算法的时间复杂度。

  5. 实验结果:实验结果通常显示A算法能够有效地找到解决方案。对于八数码问题,A算法能够找到最优解;对于5*5迷宫问题,它也能找到一条最短路径。然而,算法的效率会受到启发式函数选择和问题实例难度的影响。

  6. 结论:A*算法是解决八数码问题和迷宫寻路问题的强大工具,但其性能依赖于合适的启发式函数和高效的实现。通过实验,可以发现适当的优化和启发式函数的选择对于提高算法效率至关重要。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 实验目的 通过实现A*算法求解八数码问题,加深对A*算法的理解。 2. 实验原理 A*算法是一种启发式搜索算法,可以用于求解最短路径问题。与Dijkstra算法类似,A*算法也是通过维护一个开放列表和一个闭合列表来实现的,但A*算法在选择下一个节点时,不仅考虑到从起点到该节点的距离,还考虑到从该节点到终点的估计距离,即启发式函数。启发式函数可以用来指导搜索方向,从而减少搜索的节点数,提高搜索效率。 对于八数码问题,可以将每一个状态看作一个节点,通过交换数字来达到目标状态。可以用曼哈顿距离来估计当前状态到目标状态的距离,即h值。曼哈顿距离是指两个点在网格状的平面上的距离,即从一个点到另一个点沿着网格线所需的步数。具体计算方式如下: 对于一个数字i,设它当前的位置为(x1,y1),目标位置为(x2,y2),则i的曼哈顿距离为:|x1-x2|+|y1-y2| 对于一个状态,设其当前的曼哈顿距离为h,从起点到该状态的实际距离为g,则该状态的f值为f=g+h。 A*算法的具体过程如下: 1. 将起点加入开放列表,并将其f值设为0。 2. 从开放列表中选择f值最小的节点作为当前节点,将其移入闭合列表。 3. 对当前节点的相邻节点进行扩展,将它们加入开放列表,并计算它们的f值。 4. 重复步骤2-3,直到找到终点或开放列表为空。 5. 如果找到了终点,从终点开始沿着父节点指针回溯,直到回溯到起点,得到路径。 3. 实验步骤 1. 设计状态表示:使用三维数组来表示状态,其中第一维表示行,第二维表示列,第三维表示数字。 2. 设计节点类:节点类包含当前状态、父节点指针、f值和g值等成员变量和相应的成员函数。 3. 设计启发式函数:使用曼哈顿距离来计算h值。 4. 实现A*算法:实现开放列表和闭合列表的维护,以及节点的扩展和f值的计算等操作。 5. 测试算法:生成随机初始状态,调用A*算法求解,输出解路径和搜索过程中扩展的节点数。 4. 实验结果 以下是一个随机生成的初始状态的解路径和搜索过程中扩展的节点数: 初始状态: 7 2 4 5 0 6 8 3 1 解路径: 7 2 4 5 3 6 8 0 1 搜索过程中扩展的节点数:65 5. 实验总结 通过实现A*算法求解八数码问题,加深了对A*算法的理解。A*算法在搜索过程中能够利用启发式函数来指导搜索方向,从而减少搜索的节点数,提高搜索效率。对于八数码问题,曼哈顿距离可以作为启发式函数来估计当前状态到目标状态的距离。实际应用中,A*算法可以用于求解迷宫问题、路径规划等问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值