Python3 A*寻路算法实现

# -*- coding: utf-8 -*-
import math
import random
import copy
import time
import sys
import tkinter
import threading

# 地图
tm = [
'############################################################',
'#S............................#............#.....#.........#',
'#..........#..................#......#.....#.....#.........#',
'#..........#..................#......#.....#.....#.........#',
'#..........#..................#......#.....#.....#.........#',
'#..........#.........................#.....#.....#.........#',
'#..........#..................#......#.....#...............#',
'#..#########..................#......#.....#.....#.........#',
'#..#..........................#......#.....#.....#.........#',
'#..#..........................#......#.....#.....#.........#',
'#..############################......#.....#.....#.........#',
'#.............................#......#.....#.....#.........#',
'#.............................#......#...........#.........#',
'#######.##################################################.#',
'#....#........#.................#.............#............#',
'#....#........#........#........#.............#............#',
'#....####.#####........#........#.............#............#',
'#.........#............#........#.............#............#',
'#.........#............#........#.............#............#',
'#.........#............#........#.............#............#',
'#.........#............#........#.............#............#',
'#.........#............#........#.............#............#',
'#.........#............#........####.#######.##............#',
'#.........#............#........#....#.......#.............#',
'#.........#............#........#....#.......#.............#',
'#......................#........#....#.......#.............#',
'#.........#............#........##.########..#.............#',
'#.........#............#..................#..########.######',
'#.........#............#..................#...............E#',
'############################################################']

# 存储搜索时的地图
test_map = []


#----------- 开放列表和关闭列表的元素类型,parent用来在成功的时候回溯路径 -----------
class Node_Elem:

    def __init__(self, parent, x, y, dist):
        self.parent = parent  # 回溯父节点
        self.x = x  # x坐标
        self.y = y  # y坐标
        self.dist = dist  # 从起点到此位置的实际距离


#----------- A*算法 -----------
class A_Star:
    
    def __init__(self, root, s_x, s_y, e_x, e_y, w=60, h=30):

        self.s_x = s_x  # 起点x
        self.s_y = s_y  # 起点y
        self.e_x = e_x  # 终点x
        self.e_y = e_y  # 终点y
        
        self.open = []  # open表
        self.close = []  # close表
        self.path = []  # path表

        # 创建画布
        self.root = root  # 画布根节点
        self.width = w  # 地图w,默认60
        self.height = h  # 地图h,默认30
        self.__r = 3  # 半径
        # Tkinter.Canvas
        self.canvas = tkinter.Canvas(
                root,
                width=self.width * 10 + 100,
                height=self.height * 10 + 100,
                bg="#EBEBEB",  # 背景白色 
                xscrollincrement=1,
                yscrollincrement=1
            )
        self.canvas.pack(expand=tkinter.YES, fill=tkinter.BOTH)
        self.title("A*迷宫算法(e:开始搜索或退出)")
        self.__bindEvents()
        self.new()

    # 按键响应程序
    def __bindEvents(self):

        self.root.bind("e", self.quite)  # 退出程序

    # 退出程序
    def quite(self, evt):
        self.root.destroy()

    # 更改标题
    def title(self, s):
        self.root.title(s)

    # 初始化
    def new(self):

        node = self.canvas.create_oval(100 - self.__r,
                            20 - self.__r, 100 + self.__r, 20 + self.__r,
                            fill="#ff0000",
                            outline="#ffffff",
                            tags="node",
                        )
        self.canvas.create_text(130, 20,
                            text=u'Wall',
                            fill='black'
                        )
        node = self.canvas.create_oval(200 - self.__r,
                            20 - self.__r, 200 + self.__r, 20 + self.__r,
                            fill="#00ff00",
                            outline="#ffffff",
                            tags="node",
                        )
        self.canvas.create_text(230, 20,
                            text=u'Path',
                            fill='black'
                        )
        node = self.canvas.create_oval(300 - self.__r,
                            20 - self.__r, 300 + self.__r, 20 + self.__r,
                            fill="#AAAAAA",
                            outline="#ffffff",
                            tags="node",
                        )
        self.canvas.create_text(330, 20,
                            text=u'Searched',
                            fill='black'
                        )

        for i in range(self.width):
            for j in range(self.height):
                # 生成障碍节点,半径为self.__r
                if test_map[j][i] == '#':
                    node = self.canvas.create_oval(i * 10 + 50 - self.__r,
                            j * 10 + 50 - self.__r, i * 10 + 50 + self.__r, j * 10 + 50 + self.__r,
                            fill="#ff0000",  # 填充红色
                            outline="#ffffff",  # 轮廓白色
                            tags="node",
                        )
                # 显示起点
                if test_map[j][i] == 'S':
                    node = self.canvas.create_oval(i * 10 + 50 - self.__r,
                            j * 10 + 50 - self.__r, i * 10 + 50 + self.__r, j * 10 + 50 + self.__r,
                            fill="#00ff00",  # 填充绿色
                            outline="#ffffff",  # 轮廓白色
                            tags="node",
                        )
                    self.canvas.create_text(i * 10 + 50, j * 10 + 50 - 20,  # 使用create_text方法在坐标处绘制文字  
                            text=u'Start',  # 所绘制文字的内容  
                            fill='black'  # 所绘制文字的颜色为灰色
                        )
                # 显示终点
                if test_map[j][i] == 'E':
                    node = self.canvas.create_oval(i * 10 + 50 - self.__r,
                            j * 10 + 50 - self.__r, i * 10 + 50 + self.__r, j * 10 + 50 + self.__r,
                            fill="#00ff00",  # 填充绿色
                            outline="#ffffff",  # 轮廓白色
                            tags="node",
                        )
                    self.canvas.create_text(i * 10 + 50, j * 10 + 50 + 20,  # 使用create_text方法在坐标处绘制文字  
                            text=u'End',  # 所绘制文字的内容  
                            fill='black'  # 所绘制文字的颜色为灰色
                        )
                # 生成路径节点,半径为self.__r
                if test_map[j][i] == '*':
                    node = self.canvas.create_oval(i * 10 + 50 - self.__r,
                            j * 10 + 50 - self.__r, i * 10 + 50 + self.__r, j * 10 + 50 + self.__r,
                            fill="#0000ff",  # 填充蓝色
                            outline="#ffffff",  # 轮廓白色
                            tags="node",
                        )
                # 生成搜索区域,半径为self.__r
                if test_map[j][i] == ' ':
                    node = self.canvas.create_oval(i * 10 + 50 - self.__r,
                            j * 10 + 50 - self.__r, i * 10 + 50 + self.__r, j * 10 + 50 + self.__r,
                            fill="#AAAAAA",  # 填充白色
                            outline="#ffffff",  # 轮廓白色
                            tags="node",
                        )
        
    # 查找路径的入口函数
    def find_path(self):
        # 构建开始节点
        p = Node_Elem(None, self.s_x, self.s_y, 0.0)
        while True:
            # 扩展节点
            self.extend_round(p)
            # 如果open表为空,则不存在路径,返回
            if not self.open:
                return
            # 取F值最小的节点
            idx, p = self.get_best()
            # 到达终点,生成路径,返回
            if self.is_target(p):
                self.make_path(p)
                return
            # 把此节点加入close表,并从open表里删除
            self.close.append(p)
            del self.open[idx]

    # 生成路径    
    def make_path(self, p):
        # 从结束点回溯到开始点,开始点的parent == None
        while p:
            self.path.append((p.x, p.y))
            p = p.parent

    # 判断是否为终点
    def is_target(self, i):
        return i.x == self.e_x and i.y == self.e_y

    # 取F值最小的节点
    def get_best(self):
        best = None
        bv = 10000000  # MAX值
        bi = -1
        for idx, i in enumerate(self.open):
            value = self.get_dist(i)
            if value < bv:
                best = i
                bv = value
                bi = idx
        return bi, best
        
    # 求距离
    def get_dist(self, i):
        # F = G + H
        # G 为当前路径长度,H为估计长度
        return i.dist + math.sqrt((self.e_x - i.x) * (self.e_x - i.x)) + math.sqrt((self.e_y - i.y) * (self.e_y - i.y))

    # 扩展节点
    def extend_round(self, p):
        # 八个方向移动
        xs = (-1, 0, 1, -1, 1, -1, 0, 1)
        ys = (-1, -1, -1, 0, 0, 1, 1, 1)
        # 上下左右四个方向移动
        xs = (0, -1, 1, 0)
        ys = (-1, 0, 0, 1)
        for x, y in zip(xs, ys):
            new_x, new_y = x + p.x, y + p.y
            # 检查位置是否合法
            if not self.is_valid_coord(new_x, new_y):
                continue
            # 构造新的节点,计算距离
            node = Node_Elem(p, new_x, new_y, p.dist + self.get_cost(
                        p.x, p.y, new_x, new_y))
            # 新节点在关闭列表,则忽略
            if self.node_in_close(node):
                continue
            i = self.node_in_open(node)
            # 新节点在open表
            if i != -1:
                # 当前路径距离更短
                if self.open[i].dist > node.dist:
                    # 更新距离
                    self.open[i].parent = p
                    self.open[i].dist = node.dist
                continue
            # 否则加入open表
            self.open.append(node)

    # 移动距离,直走1.0,斜走1.4
    def get_cost(self, x1, y1, x2, y2):
        if x1 == x2 or y1 == y2:
            return 1.0
        return 1.4
    
    # 检查节点是否在close表
    def node_in_close(self, node):
        for i in self.close:
            if node.x == i.x and node.y == i.y:
                return True
        return False
    
    # 检查节点是否在open表,返回序号  
    def node_in_open(self, node):
        for i, n in enumerate(self.open):
            if node.x == n.x and node.y == n.y:
                return i
        return -1

    # 判断位置是否合法,超出边界或者为阻碍    
    def is_valid_coord(self, x, y):
        if x < 0 or x >= self.width or y < 0 or y >= self.height:
            return False
        return test_map[y][x] != '#'

    # 搜寻过的位置
    def get_searched(self):
        l = []
        for i in self.open:
            l.append((i.x, i.y))
        for i in self.close:
            l.append((i.x, i.y))
        return l


# 获取起点坐标
def get_start_XY():
    return get_symbol_XY('S')


# 获取终点坐标
def get_end_XY():
    return get_symbol_XY('E')


# 查找特定元素
def get_symbol_XY(s):
    for y, line in enumerate(test_map):
        try:
            x = line.index(s)
        except:
            continue
        else:
            break
    return x, y

        
# 标记路径位置 
def mark_path(l):
    mark_symbol(l, '*')


# 标记已搜索过的位置    
def mark_searched(l):
    mark_symbol(l, ' ')


# 标记函数
def mark_symbol(l, s):
    for x, y in l:
        test_map[y][x] = s

        
# 标记起点和终点
def mark_start_end(s_x, s_y, e_x, e_y):
    test_map[s_y][s_x] = 'S'
    test_map[e_y][e_x] = 'E'


# 将地图字符串转化为表
def tm_to_test_map():
    for line in tm:
        test_map.append(list(line))

        
# 寻找路径        
def find_path():
    s_x, s_y = get_start_XY()
    e_x, e_y = get_end_XY()
    # A*算法
    a_star = A_Star(tkinter.Tk(), s_x, s_y, e_x, e_y)
    a_star.root.mainloop()
    a_star.find_path()
    searched = a_star.get_searched()
    path = a_star.path
    # 标记已搜索过的位置
    mark_searched(searched)
    # 标记路径位置 
    mark_path(path)
    # 标记起点和终点
    mark_start_end(s_x, s_y, e_x, e_y)
    print(u"路径长度:%d" % (len(path)))
    print(u"搜索过的区域:%d" % (len(searched)))
    a_star = A_Star(tkinter.Tk(), s_x, s_y, e_x, e_y)
    a_star.root.mainloop()
    
#----------- 程序的入口处 -----------

                
if __name__ == '__main__':

    print (u""" 
--------------------------------------------------------
    程序:A*迷宫问题程序 
    作者:Gm
    日期:2019-7-08
    语言:Python 3.7 
-------------------------------------------------------- 
    """)
    # 载入地图
    tm_to_test_map()
    # 寻找路径
    find_path()

在这里插入图片描述

posted on 2019-07-08 13:12  疯狂的小萝卜头  阅读(310)  评论(0编辑  收藏  举报