\documentclass{article} \usepackage{graphicx} % Required for inserting images \usepackage[chinese]{babel} \usepackage[UTF8]{ctex} \usepackage{float} \usepackage[letterpaper,top=2cm,bottom=2cm,left=3cm,right=3cm,marginparwidth=1.75cm]{geometry} \usepackage{listings} \usepackage{authblk} % Useful packages \usepackage{amsmath} \usepackage{subfigure} \usepackage{graphicx} \usepackage[colorlinks=true, allcolors=blue]{hyperref}
\title{2023算法设计大作业} \author[]{21009201106-潘笃驿} \author[]{21009201203-曹宇思} \author[]{21009201084代淑婷} \author[]{21009200904李泽荟} \author[]{21009201005 毛美文} \author[]{21009200999余菲月}
\affil[*]{算法第六组}
\renewcommand\Authands{ and }
\date{October 2023}
\begin{document}
\maketitle
\section{迷宫设计}
\subsection{迷宫初始化} 我们使用随机化的Prim算法来创建迷宫。
Prim算法是一种图论中的算法,用于找到加权连通图的最小生成树。在这个迷宫生成器中,Prim算法被用于创建迷宫的通路,而迷宫的墙则是最小生成树之外的部分。
我们可以把迷宫看做这个样子,左边是用类似数组表示的迷宫,把墙简化后比较容易看
\begin{figure}[H] \centering \includegraphics[width=0.5\linewidth]{image01.png} \caption{01} \label{fig:galaxy} \end{figure}
这是迷宫初始化的样子,所有墙壁都是封死的,路径格处于独立状态。如果把迷宫看作带权图G,则点集V即4为简化图中显示的9个路径格,边集E则是任意相邻两路径格间连线的集合,可以看作所有路径权值都相等。
设迷宫入口为左上角的路径格,出口为右下角的路径格,则生成一个迷宫的实质就是找G的一个最小生成树T,T包含所有路径格,不成环,且其中任意两个路径格连通。(事实上当不必连通所有格,只要连通了起点和终点,也可以算迷宫生成完毕,只是这样的迷宫不完整)
把起点加入空集A,则森林Ga中包含9棵根节点状态的树,接下来要不断在E中找安全边,使Ga中的根节点状态的树元素都加入到集合A中,直到Ga中只剩A一个元素(或者Ga中某树包含起点和终点)为止。
在具体实现中我们可以按照下面的步骤来做 \begin{enumerate} \item[1).] 让迷宫全是墙 \item[2).] 选一个单元格作为迷宫的通路,然后把它的邻墙放入列表 \item[3).] 当列表里还有墙时从列表里随机选一个墙,如果这面墙分隔的两个单元格只有一个单元格被访问过,那就从列表里移除这面墙,即把墙打通,让未访问的单元格成为迷宫的通路,再把这个格子的墙加入列表 \item[4).] 如果墙两面的单元格都已经被访问过,那就从列表里移除这面墙 \item[5).] 列表里已经没有墙了,结束 \end{enumerate}
实现代码如下: \begin{lstlisting}[language=python] from random import randint, choice from enum import Enum
迷宫通路、墙、起点和终点
class MAP_ENTRY_TYPE (Enum): MAP_EMPTY = 0, MAP_BLOCK = 1, MAP_START = 2, MAP_END = 3,
class WALL_DIRECTION(Enum): WALL_LEFT = 0, WALL_UP = 1, WALL_RIGHT = 2, WALL_DOWN = 3,
class Map(): def init(self, width, height): self.width = width self.height = height self.map = [[0 for x in range(self.width)] for y in range(self.height)]
def resetMap(self, value):
for y in range(self.height):
for x in range(self.width):
self.setMap(x, y, value)
# 地图的设置
def setMap(self, x, y, value):
if value == MAP_ENTRY_TYPE.MAP_EMPTY:
self.map[y][x] = 0
elif value == MAP_ENTRY_TYPE.MAP_BLOCK:
self.map[y][x] = 1
elif value == MAP_ENTRY_TYPE.MAP_START:
self.map[y][x] = 2
elif value == MAP_ENTRY_TYPE.MAP_END:
self.map[y][x] = 3
# 判断是否已经访问过(访问过则代表不设定为墙
def isVisited(self, x, y):
return self.map[y][x] != 1
# 将生成的地图写入data.txt文件
def showMap(self):
# coding=UTF-8
filename = 'data.txt'
# 初始化data文件,删除原有内容成为空白文件
with open(filename, 'w') as file_object:
file_object.write("")
for row in self.map:
s = " '"
with open(filename, 'a') as file_object:
file_object.write(" '")
for entry in row:
# 通路
if entry == 0:
s += '.'
with open(filename, 'a') as file_object:
file_object.write(".")
# 墙
elif entry == 1:
s += '#'
with open(filename, 'a') as file_object:
file_object.write("#")
# 起点
elif entry == 2:
s += 'S'
with open(filename, 'a') as file_object:
file_object.write("S")
# 终点
else:
s += 'E'
with open(filename, 'a') as file_object:
file_object.write("E")
s += "',"
with open(filename, 'a') as file_object:
file_object.write("',\n")
print(s)
找出与当前位置相邻的墙(四个方向
然后将其中一个方向的墙随机添加到checklist中,并将其标记为已访问
def checkAdjacentPos(map, x, y, width, height, checklist): directions = [] # 如果当前位置处在边界(即最多只有三个相邻墙位置 if x > 0: if not map.isVisited(2 * (x - 1) + 1, 2 * y + 1): directions.append(WALL_DIRECTION.WALL_LEFT)
if y > 0:
if not map.isVisited(2 * x + 1, 2 * (y - 1) + 1):
directions.append(WALL_DIRECTION.WALL_UP)
if x < width - 1:
if not map.isVisited(2 * (x + 1) + 1, 2 * y + 1):
directions.append(WALL_DIRECTION.WALL_RIGHT)
if y < height - 1:
if not map.isVisited(2 * x + 1, 2 * (y + 1) + 1):
directions.append(WALL_DIRECTION.WALL_DOWN)
# 如果当前位置四周的相邻结点有墙
if len(directions):
# 随机选择一个方向的墙
direction = choice(directions)
if direction == WALL_DIRECTION.WALL_LEFT:
map.setMap(2 * (x - 1) + 1, 2 * y + 1, MAP_ENTRY_TYPE.MAP_EMPTY)
map.setMap(2 * x, 2 * y + 1, MAP_ENTRY_TYPE.MAP_EMPTY)
checklist.append((x - 1, y))
elif direction == WALL_DIRECTION.WALL_UP:
map.setMap(2 * x + 1, 2 * (y - 1) + 1, MAP_ENTRY_TYPE.MAP_EMPTY)
map.setMap(2 * x + 1, 2 * y, MAP_ENTRY_TYPE.MAP_EMPTY)
checklist.append((x, y - 1))
elif direction == WALL_DIRECTION.WALL_RIGHT:
map.setMap(2 * (x + 1) + 1, 2 * y + 1, MAP_ENTRY_TYPE.MAP_EMPTY)
map.setMap(2 * x + 2, 2 * y + 1, MAP_ENTRY_TYPE.MAP_EMPTY)
checklist.append((x + 1, y))
elif direction == WALL_DIRECTION.WALL_DOWN:
map.setMap(2 * x + 1, 2 * (y + 1) + 1, MAP_ENTRY_TYPE.MAP_EMPTY)
map.setMap(2 * x + 1, 2 * y + 2, MAP_ENTRY_TYPE.MAP_EMPTY)
checklist.append((x, y + 1))
return True
else:
# 找不到四周有未经访问的墙(即周边的墙都已经访问过
return False
随机prim算法
def randomPrim(map, width, height): # 起始点的设置 startX, startY = (0, 0) map.setMap(2startX+0, 2startY+1, MAP_ENTRY_TYPE.MAP_START) # 终点的设置 endX, endY = (width, height) map.setMap(2endX-0, 2endY-1, MAP_ENTRY_TYPE.MAP_END)
checklist = []
checklist.append((startX, startY))
while len(checklist):
# 从checklist中选择一个随机方向的墙
entry = choice(checklist)
if not checkAdjacentPos(map, entry[0], entry[1], width, height, checklist):
# 如果四周的墙都已访问过,则把该结点位置从checklist中删除
checklist.remove(entry)
def doRandomPrim(map): # 将地图的所有结点位置都设置为墙 map.resetMap(MAP_ENTRY_TYPE.MAP_BLOCK) randomPrim(map, (map.width - 1) // 2, (map.height - 1) // 2)
def run(): WIDTH = 41 HEIGHT = 31 map = Map(WIDTH, HEIGHT) doRandomPrim(map) map.showMap()
if name == “main”: run() \end{lstlisting}
\subsection{迷宫交互设计} 完成迷宫设计后,我们利用python中的Turtle库来绘制迷宫,使用turtle库来创建游戏窗口,设置窗口大小和背景色,注册迷宫中使用到的图片资源(墙、玩家角色、迷宫入口)。
我们设计了两个类
画笔类(Pen):
Pen类继承自t.Turtle,用于绘制迷宫。
在 \verb | make_maze | 方法中,根据迷宫地图绘制迷宫的墙。 |
玩家类(Player):
Player类也继承自t.Turtle,用于控制玩家角色。
提供了四个方向的移动方法:\verb | go_right | , \verb | go_left | , \verb | go_up | , \verb | go_down | 。 |
在移动方法中,玩家角色会改变形状以显示移动状态,并且只有在没有撞到墙的情况下才会移动。
具体代码如下:
\begin{lstlisting}[language=python] #1.创建游戏背景 import turtle as t
import random
创建窗口
mz = t.Screen()
设置长宽
mz.setup(520,400)
设置背景色
mz.bgcolor(‘#EBEBEB’)
设置标题
mz.title(‘MoveMaze’)
使用照片前先将照片注册
mz.register_shape(‘images/wall.gif’) mz.register_shape(‘images/pr.gif’) mz.register_shape(‘images/la.gif’)
不用一步一步显示画面,最后一起显示,和mz.update()配合用
mz.tracer(0)
r_map = []
P:player;
map = [ ‘#########################################’, ‘S………….#.#…#……………….#’, ‘#.#.#####.#.###.#.###.###########.#.###.#’, ‘#.#…..#.#…..#…..#…#…..#.#…#.#’, ‘###.#####.#######.#####.###.###.#######.#’, ‘#…….#……………..#…#.#…#.#.#’, ‘###.#######################.#####.###.#.#’, ‘#………#.#…………………..#.#.#’, ‘#.#####.#.#.#.#############.#####.###.###’, ‘#.#…..#…#………….#…..#…….#’, ‘#.#####.#####.#######.###.#.#.###.#.#.#.#’, ‘#.#……………#…..#.#.#…#.#.#.#.#’, ‘#.#####.#.#.#.#############.###########.#’, ‘#.#…..#.#.#………….#………#.#.#’, ‘#.###.#.#########.#.#######.#####.###.###’, ‘#…#.#………#.#…….#…..#.#…..#’, ‘###.#.#.#.#.#.###.#.#####.#########.#.###’, ‘#…#.#.#.#.#…#.#…..#………..#.#.#’, ‘#.#.#.###.###.###.#########.#.#.#.#####.#’, ‘#.#.#.#…..#…#…..#…#.#.#.#…….#’, ‘#.#.#.#.#####.#.#.#.#.#.#######.#.#.#####’, ‘#.#.#.#…..#.#.#.#.#………#.#.#…..#’, ‘#.#.###.#####.###.#.#.#.#####.#.#.###.###’, ‘#.#…#.#…….#.#.#.#…..#.#.#…#…#’, ‘#.#.#.#.#####.###.#.#.###.#####.#.#.###.#’, ‘#.#.#.#…..#…#.#.#.#.#…#…#.#…#.#’, ‘###.#.#########.#.#.#.#.###########.#.#.#’, ‘#…#………#.#.#.#………….#.#.#.#’, ‘#.#.#.###.#####.###.#.#.###.#.###.#.#####’, ‘#.#.#…#…#…..#.#.#…#.#…#.#…..E’, ‘#########################################’, ]
r_map.append(map)
class Pen(t.Turtle): # ———–画笔————- def init(self): # 继承父类的属性 super().init() # 先隐藏起来,隐秘进行运动 self.ht() self.shape(‘images/wall.gif’) self.speed(0) self.penup()
# -----------迷宫--------------
def make_maze(self):
level = r_map[current - 1]
for i in range(len(level)):
# 取出某一行
row = level[i]
# 获取到某一元素的坐标
for j in range(len(row)):
screen_x = -244 + 12 * j
screen_y = 185 - 12 * i
char = row[j]
#如果元素为X,则画出迷宫
if char == '#':
self.goto(screen_x,screen_y)
self.stamp()
walls.append((screen_x,screen_y))#元组
elif char == 'S':
player.goto(screen_x,screen_y)
player.st()
class Player(t.Turtle): #当前位置的移动 def init(self): super().init() # 先隐藏起来,隐秘进行运动 self.ht() self.shape(‘images/pr.gif’) self.speed(0) self.penup()
# 右移
def go_right(self):
# print('going right')
self.shape('images/la.gif')
self.stamp()
go_x = self.xcor() + 12
go_y = self.ycor()
self.shape('images/pr.gif')
self.move(go_x, go_y)
# 左移
def go_left(self):
# print('going left')
self.shape('images/la.gif')
self.stamp()
go_x = self.xcor() - 12
go_y = self.ycor()
self.shape('images/pr.gif')
self.move(go_x,go_y)
# 上移
def go_up(self):
# print('going up')
self.shape('images/la.gif')
self.stamp()
go_x = self.xcor()
go_y = self.ycor() + 12
self.shape('images/pr.gif')
self.move(go_x, go_y)
# 下移
def go_down(self):
# print('going down')
self.shape('images/la.gif')
self.stamp()
go_x = self.xcor()
go_y = self.ycor() - 12
self.shape('images/pr.gif')
self.move(go_x, go_y)
# 运动时的共同行为
def move(self, go_x, go_y):
if (go_x, go_y) not in walls:
self.goto(go_x, go_y)
else:
print('撞墙了')
current = 1
调用画笔类
pen = Pen()
调用玩家
player = Player()
walls数组来存储墙的坐标
walls = []
根据不同的关卡绘制迷宫
pen.make_maze()
根据键盘移动
mz.listen() mz.onkey(player.go_right, ‘Right’) mz.onkey(player.go_left, ‘Left’) mz.onkey(player.go_up, ‘Up’) mz.onkey(player.go_down, ‘Down’)
while True: mz.update()
mz.mainloop()
\end{lstlisting}
\section{A*算法求解迷宫}
\subsection{A*算法介绍与实现}
A*算法最初发表于1968年,由Stanford研究院的Peter Hart, Nils Nilsson以及Bertram Raphael发表。它可以被认为是Dijkstra算法的扩展。
由于借助启发函数的引导,A算法通常拥有更好的性能。A吸取了Dijkstra 算法中的优点,为每个边长设置权值,不停的计算每个顶点到起始顶点的距离(G),以获得最短路线,同时也汲取贪婪最佳优先搜索算法中不断向目标前进优势,并持续计算每个顶点到目标顶点的距离(Heuristic distance),以引导搜索队列不断想目标逼近,从而搜索更少的顶点,保持寻路的高效
启发函数会影响A*算法的行为。在极端情况下,当启发函数h(n)始终为0,则将由g(n)决定节点的优先级,此时算法就退化成了Dijkstra算法。
如果h(n)始终小于等于节点n到终点的代价,则A*算法保证一定能够找到最短路径。但是当h(n)的值越小,算法将遍历越多的节点,也就导致算法越慢。
如果h(n)完全等于节点n到终点的代价,则A算法将找到最佳路径,并且速度很快。可惜的是,并非所有场景下都能做到这一点。因为在没有达到终点之前,我们很难确切算出距离终点还有多远。 如果h(n)的值比节点n到终点的代价要大,则A算法不能保证找到最短路径,不过此时会很快。 在另外一个极端情况下,如果h()n相较于g(n)大很多,则此时只有h(n)产生效果,这也就变成了最佳优先搜索。
由上面这些信息我们可以知道,通过调节启发函数我们可以控制算法的速度和精确度。因为在一些情况,我们可能未必需要最短路径,而是希望能够尽快找到一个路径即可。这也是A*算法比较灵活的地方。
对于网格形式的图,有以下这些启发函数可以使用:
如果图形中只允许朝上下左右四个方向移动,则可以使用曼哈顿距离(Manhattan distance)。 如果图形中允许朝八个方向移动,则可以使用对角距离。 如果图形中允许朝任何方向移动,则可以使用欧几里得距离(Euclidean distance)。
在实验中我们选择曼哈顿距离作为启发函数
具体实现如下:
\begin{lstlisting}[language=python]
-- coding: utf-8 --
import math import tkinter from tkinter import *
DrawMap生成的地图导入
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=41, h=31):
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,默认41
self.height = h # 地图h,默认31
self.__r = 5 # 半径
# 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*Maze")
self.__bindEvents()
self.new()
# 按键响应程序
def __bindEvents(self):
self.root.bind("<Return>", self.quite) # 退出程序
# 退出程序
def quite(self, evt):
self.root.destroy()
# 更改标题
def title(self, s):
self.root.title(s)
# 初始化
def new(self):
# 提示墙的颜色为黑色
node = self.canvas.create_rectangle(100 - self.__r,
20 - self.__r, 100 + self.__r, 20 + self.__r,
fill="#000000",
outline="#ffffff",
tags="node",
)
self.canvas.create_text(130, 20,
text=u'Wall',
fill='black'
)
# 提示通路的颜色为绿色
node = self.canvas.create_rectangle(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'
)
# 提示按下enter键可以寻路
self.canvas.create_text(350, 20,
text=u'Enter:find way',
fill='black'
)
for i in range(self.width):
for j in range(self.height):
# 生成墙壁
if test_map[j][i] == '#':
node = self.canvas.create_rectangle(i * 10 + 50 - self.__r,
j * 10 + 50 - self.__r, i * 10 + 50 + self.__r,
j * 10 + 50 + self.__r,
fill="#000000", # 填充黑色
outline="#ffffff", # 轮廓白色
tags="node",
)
# 显示起点
if test_map[j][i] == 'S':
node = self.canvas.create_rectangle(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_rectangle(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' # 所绘制文字的颜色为灰色
)
# 生成通路
if test_map[j][i] == '*':
node = self.canvas.create_rectangle(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",
)
# 查找路径的入口函数
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 = (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_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_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() path = a_star.path # 标记路径位置 mark_path(path) # 标记起点和终点 mark_start_end(s_x, s_y, e_x, e_y) print(u”路径长度:%d” % (len(path))) a_star = A_Star(tkinter.Tk(), s_x, s_y, e_x, e_y) a_star.root.mainloop()
程序的入口处
if name == ‘main’: # 载入地图 tm_to_test_map() # 寻找路径 find_path() \end{lstlisting}
\subsection{复杂度分析}
时间复杂度: 在最坏情况下,如果启发式函数h(n)始终返回0(即没有提供任何有用的信息),A算法将退化为Dijkstra算法。在这种情况下,时间复杂度取决于搜索空间的规模。对于网格中的搜索,如果每个节点平均有b个邻居,并且搜索空间中有N个节点,那么最坏情况下的时间复杂度可能是$O(b^N)$。然而,这在实际应用中几乎不会发生,因为启发式函数通常会提供有用的信息来指导搜索。 在平均情况下或启发式函数提供良好估计的情况下,A算法的时间复杂度可能会显著降低。实际的时间复杂度取决于启发式函数的质量以及图的结构。
空间复杂度:
A*算法需要维护一个开放列表(open list)和一个关闭列表(closed list)。开放列表包含待探索的节点,而关闭列表包含已探索的节点。在最坏情况下,如果所有节点都需要被探索,空间复杂度可能会达到O(N),其中N是图中节点的数量。
在实践中,通过使用有效的数据结构(如优先队列)来管理开放列表,并适时地从列表中移除不必要的节点,可以进一步优化空间复杂度。
启发式函数的影响:
如果启发式函数h(n)是可采纳的(即它从不高估从当前节点到目标的实际代价),并且是一致的(即对于任意节点n和通过任何一步到达的邻居节点$n’$,有$h(n) ≤ d(n, n’) + h(n’)$,其中$d(n, n’)$是从n到n’的实际代价),那么A*算法将保证找到最短路径。曼哈顿距离通常用作网格上的启发式函数,因为它满足这些条件。
启发式函数的质量会影响A*算法的搜索效率。一个好的启发式函数能够更准确地估计从当前节点到目标的代价,从而减少搜索所需探索的节点数量。
\end{document}