2026游戏安全技术竞赛-PC客户端安全-初赛 求解起点到终点的最短路径

四、求解起点到终点的最短路径

求解程序:

#!/usr/bin/env python3
import argparse
import json
import time
from typing import List, Tuple, Dict, Optional, Set
from collections import deque
import heapq
import sys
class MazeSolver:
"""
迷宫路径求解器 """
def init(self, maze_data: ListList\[int]):
self.maze = maze_data
self.rows = len(maze_data)
self.cols = len(maze_data0) if self.rows > 0 else 0
#
默认起点和终点
self.start = self._find_cell(2) or (0, 0)
self.end = self._find_cell(3) or (self.rows - 1, self.cols - 1)
#
统计信息
self.stats = {
'nodes_explored': 0,
'max_queue_size': 0,
'time_taken': 0.0,
'path_length': 0
}
def _find_cell(self, value: int) -> OptionalTuple\[int, int]:
"""
查找指定值的单元格 """
for y in range(self.rows):
for x in range(self.cols):
if self.mazeyx == value:
return (x, y)
return None
def is_valid(self, x: int, y: int) -> bool:
"""
检查坐标是否有效 """
return 0 <= x < self.cols and 0 <= y < self.rows
def is_passable(self, x: int, y: int) -> bool:
"""
检查位置是否可通过 """
return self.is_valid(x, y) and self.mazeyx != 1
def set_start_end(self, start: Tupleint, int, end: Tupleint, int):
"""
设置起点和终点 """
if not (self.is_valid(*start) and self.is_passable(*start)):
raise ValueError(f"
起点 {start} 无效 ")
if not (self.is_valid(*end) and self.is_passable(*end)):
raise ValueError(f"
终点 {end} 无效 ")
self.start = start
self.end = end
def bfs(self) -> OptionalList\[Tuple\[int, int]]:
"""
BFS
算法求解最短路径

Returns: 路径列表 (x, y), ... ,如果无解返回 None
"""
print("\n" + "="*60)
print("BFS (
广度优先搜索 ) 算法 ")
print("="*60)
start_time = time.time()
queue = deque((self.start, \[self.start)])
visited = {self.start}
self.stats'nodes_explored' = 0
self.stats'max_queue_size' = 1
directions = (0, 1), (0, -1), (1, 0), (-1, 0) #
下、上、右、左

while queue:
self.stats'max_queue_size' = max(self.stats'max_queue_size', len(queue))
current, path = queue.popleft()
self.stats'nodes_explored' += 1
#
到达终点
if current == self.end:
self.stats'time_taken' = time.time() - start_time
self.stats'path_length' = len(path)
print(f"+
找到最短路径 !")
print(f"
路径长度 : {len(path)} ")
return path
#
探索邻居
for dx, dy in directions:
nx, ny = current0 + dx, current1 + dy
neighbor = (nx, ny)
if self.is_passable(nx, ny) and neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + neighbor))
self.stats'time_taken' = time.time() - start_time
print("-
未找到路径 ")
return None
def astar(self, heuristic: str = 'manhattan') -> OptionalList\[Tuple\[int, int]]:
"""
A*
算法求解最短路径

Args:
heuristic:
启发式函数 ('manhattan' 'euclidean')
Returns:
路径列表
"""
print("\n" + "="*60)
print(f"A*
算法 ( 启发式 : {heuristic})")
print("="*60)
start_time = time.time()
#
优先队列 : (f_score, g_score, position, path)
heap = (0, 0, self.start, \[self.start)]
visited = {self.start: 0} #
记录到达每个点的最小 g_score
self.stats'nodes_explored' = 0
self.stats'max_queue_size' = 1
directions = (0, 1), (0, -1), (1, 0), (-1, 0)
while heap:
self.stats'max_queue_size' = max(self.stats'max_queue_size', len(heap))
f_score, g_score, current, path = heapq.heappop(heap)
self.stats'nodes_explored' += 1
#
如果找到更好的路径,跳过
if current in visited and visitedcurrent < g_score:
continue
#
到达终点
if current == self.end:
self.stats'time_taken' = time.time() - start_time
self.stats'path_length' = len(path)
print(f"+
找到最短路径 !")
print(f"
路径长度 : {len(path)} ")
print(f"
最终 f_score: {f_score:.2f}")
return path
#
探索邻居
for dx, dy in directions:
nx, ny = current0 + dx, current1 + dy
neighbor = (nx, ny)
if not self.is_passable(nx, ny):
continue
new_g_score = g_score + 1
#
如果找到了更好的路径
if neighbor not in visited or new_g_score < visited.get(neighbor, float('inf')):
visitedneighbor = new_g_score
h_score = self._calculate_heuristic(neighbor, heuristic)
new_f_score = new_g_score + h_score
heapq.heappush(
heap,
(new_f_score, new_g_score, neighbor, path + neighbor)
)
self.stats'time_taken' = time.time() - start_time
print("-
未找到路径 ")
return None
def _calculate_heuristic(self, pos: Tupleint, int, method: str) -> float:
"""
计算启发式函数值 """
if method == 'manhattan':
return abs(pos0 - self.end0) + abs(pos1 - self.end1)
elif method == 'euclidean':
return ((pos0 - self.end0) ** 2 + (pos1 - self.end1) ** 2) ** 0.5
else:
return 0
def dfs(self, max_depth: int = 10000) -> OptionalList\[Tuple\[int, int]]:
"""
DFS
算法求解路径

Args:
max_depth:
最大搜索深度

Returns: 路径列表
"""
print("\n" + "="*60)
print(f"DFS (
深度优先搜索 ) 算法 ( 最大深度 : {max_depth})")
print("="*60)
start_time = time.time()
self.stats'nodes_explored' = 0
def dfs_recursive(current: Tupleint, int, path: ListTuple\[int, int], visited: Set) -> OptionalList\[Tuple\[int, int]]:
if current == self.end:
return path:
if len(path) >= max_depth:
return None
self.stats'nodes_explored' += 1
directions = (0, 1), (0, -1), (1, 0), (-1, 0)
for dx, dy in directions:
nx, ny = current0 + dx, current1 + dy
neighbor = (nx, ny)
if self.is_passable(nx, ny) and neighbor not in visited:
visited.add(neighbor)
result = dfs_recursive(neighbor, path + neighbor, visited)
visited.remove(neighbor)
if result:
return result
return None
path = dfs_recursive(self.start, self.start, {self.start})
self.stats'time_taken' = time.time() - start_time
if path:
self.stats'path_length' = len(path)
print(f"+
找到路径 !")
print(f"
路径长度 : {len(path)} ")
else:
print("-
未找到路径 ")
return path
def bidirectional_bfs(self) -> OptionalList\[Tuple\[int, int]]:
"""
双向 BFS 算法(从起点和终点同时搜索)

Returns: 路径列表
"""
print("\n" + "="*60)
print("
双向 BFS 算法 ")
print("="*60)
start_time = time.time()
#
从起点开始的搜索
queue_start = deque((self.start, \[self.start)])
visited_start = {self.start: self.start}
#
从终点开始的搜索
queue_end = deque((self.end, \[self.end)])
visited_end = {self.end: self.end}
self.stats'nodes_explored' = 0
directions = (0, 1), (0, -1), (1, 0), (-1, 0)
def explore_step(queue, visited, other_visited):
"""
执行一步探索 """
if not queue:
return None
current, path = queue.popleft()
self.stats'nodes_explored' += 1
#
检查是否在另一个搜索树中找到
if current in other_visited:
#
合并路径
other_path = other_visitedcurrent
#
注意: other_path 需要反转(如果是从终点搜索的)
if queue == queue_end:
other_path = other_path::-1
return path + other_path1:
for dx, dy in directions:
nx, ny = current0 + dx, current1 + dy
neighbor = (nx, ny)
if self.is_passable(nx, ny) and neighbor not in visited:
visitedneighbor = path + neighbor
queue.append((neighbor, path + neighbor))
return None
iteration = 0
while queue_start and queue_end:
iteration += 1
self.stats'max_queue_size' = max(self.stats'max_queue_size',
len(queue_start) + len(queue_end))
#
交替执行
if iteration % 2 == 0:
result = explore_step(queue_start, visited_start, visited_end)
else:
result = explore_step(queue_end, visited_end, visited_start)
if result:
self.stats'time_taken' = time.time() - start_time
self.stats'path_length' = len(result)
print(f"+
找到最短路径 !")
print(f"
路径长度 : {len(result)} ")
print(f"
迭代次数 : {iteration}")
return result
self.stats'time_taken' = time.time() - start_time
print("-
未找到路径 ")
return None
def print_stats(self):
"""
打印统计信息 """
print("\n" + "-"*60)
print("
统计信息 :")
print("-"*60)
print(f"
探索的节点数 : {self.stats'nodes_explored'}")
print(f"
最大队列大小 : {self.stats'max_queue_size'}")
print(f"
耗时 : {self.stats'time_taken':.4f} ")
print(f"
路径长度 : {self.stats'path_length'} ")
print(f"
每秒探索节点数 : {self.stats'nodes_explored' / self.stats'time_taken':.2f}")
print("-"*60)
def print_path(self, path: ListTuple\[int, int]):
"""
打印路径 """
if not path:
print("\n-
无路径可显示 ")
return
print("\n" + "-"*60)
print("
路径详情 :")
print("-"*60)
print(f"
起点 : {path0}")
print(f"
终点 : {path-1}")
print(f"
总步数 : {len(path) - 1}")
print("\n
路径坐标 ( 10 步显示一次 ):")
for i, pos in enumerate(path):
if i == 0 or i == len(path) - 1 or i % 10 == 0:
direction = self._get_direction(pathi-1 if i > 0 else None, pos)
print(f"
步骤 {i:3d}: ({pos0:3d}, {pos1:3d}) {direction}")
#
计算方向统计
print("\n 方向统计 :")
direction_counts = {'
': 0, ' ': 0, ' ': 0, ' ': 0}
for i in range(len(path) - 1):
direction = self._get_direction(pathi, pathi+1, chinese=False)
if direction:
direction_countsdirection += 1
for direction, count in direction_counts.items():
if count > 0:
print(f" {direction}: {count}
({count/(len(path)-1)*100:.1f}%)")
print("-"*60)
def _get_direction(self, from_pos: OptionalTuple\[int, int],
to_pos: Tupleint, int, chinese: bool = True) -> str:
"""
获取移动方向 """
if from_pos is None:
return "
起点 " if chinese else "Start"
dx = to_pos0 - from_pos0
dy = to_pos1 - from_pos1
if chinese:
if dy > 0:
return "↓
"
elif dy < 0:
return "↑
"
elif dx > 0:
return "→
"
elif dx < 0:
return "←
"
else:
if dy > 0:
return "
"
elif dy < 0:
return "
"
elif dx > 0:
return "
"
elif dx < 0:
return "
"
return ""
def visualize_path(self, path: ListTuple\[int, int], filename: str):
"""
可视化路径并保存为图像 """
try:
from PIL import Image, ImageDraw, ImageFont
except ImportError:
print("
跳过可视化生成 ")
return
print(f"\n\*
生成可视化图像 : {filename}")
#
参数设置
cell_size = 30
padding = 10
#
计算图像尺寸
img_width = self.cols * cell_size + 2 * padding
img_height = self.rows * cell_size + 2 * padding
#
创建图像
img = Image.new('RGB', (img_width, img_height), (255, 255, 255))
draw = ImageDraw.Draw(img)
#
颜色定义
colors = {
'wall': (40, 40, 40), #
深灰色
'path': (240, 240, 240), # 浅灰色
'solution': (255, 69, 0), # 红橙色
'start': (34, 139, 34), # 森林绿
'end': (255, 215, 0), # 金色
'grid': (200, 200, 200) # 浅灰色网格
}
#
绘制迷宫
for y in range(self.rows):
for x in range(self.cols):
px = padding + x * cell_size
py = padding + y * cell_size
if self.mazeyx == 1:
#
墙壁
draw.rectangle(
px, py, px + cell_size - 1, py + cell_size - 1,
fill=colors'wall',
outline=colors'grid'
)
else:
#
通路
draw.rectangle(
px, py, px + cell_size - 1, py + cell_size - 1,
fill=colors'path',
outline=colors'grid'
)
#
绘制解路径(使用线条)
if path:
for i in range(len(path) - 1):
x1, y1 = pathi
x2, y2 = pathi + 1
px1 = padding + x1 * cell_size + cell_size // 2
py1 = padding + y1 * cell_size + cell_size // 2
px2 = padding + x2 * cell_size + cell_size // 2
py2 = padding + y2 * cell_size + cell_size // 2
draw.line((px1, py1), (px2, py2), fill=colors'solution', width=3)
#
标记起点和终点
if path:
#
起点
sx, sy = path0
px_start = padding + sx * cell_size + cell_size // 2
py_start = padding + sy * cell_size + cell_size // 2
draw.ellipse(
px_start - 8, py_start - 8, px_start + 8, py_start + 8,
fill=colors'start',
outline='black',
width=2
)
#
终点
ex, ey = path-1
px_end = padding + ex * cell_size + cell_size // 2
py_end = padding + ey * cell_size + cell_size // 2
draw.ellipse(
px_end - 8, py_end - 8, px_end + 8, py_end + 8,
fill=colors'end',
outline='black',
width=2
)
#
添加图例
legend_y = padding
legend_items = ('* *起点* *', colors\['start'),
('
终点 ', colors'end'),
('
路径 ', colors'solution'),
('
墙壁 ', colors'wall'),
('
通路 ', colors'path')
]
for text, color in legend_items:
px = img_width - 120
draw.rectangle(px, legend_y, px + 15, legend_y + 15, fill=color, outline='black')
draw.text((px + 20, legend_y), text, fill='black')
legend_y += 25
#
保存图像
img.save(filename)
print(f"+
可视化图像已保存 : {filename}")
def load_maze_from_ascii(filename: str) -> ListList\[int]:
"""
ASCII 格式文件加载迷宫 """
print(f"\*
ASCII 文件加载迷宫 : {filename}")
maze = \[\]
with open(filename, 'r', encoding='utf-8') as f:
for line in f:
line = line.rstrip('\n\r')
#
跳过空行
if not line:
continue
line_stripped = line.strip()
is_comment = False
if line_stripped.startswith('#'):
has_chinese = any('\u4e00' <= c <= '\u9fff' for c in line)
has_letters = any(c.isalpha() and c not in 'SE' for c in line)
is_short_title = len(line_stripped) < 10 and line_stripped.startswith('#')
if has_chinese or has_letters or is_short_title:
is_comment = True
if is_comment:
continue
row = \[\]
for char in line:
if char == '#':
row.append(1) #
墙壁
elif char == ' ' or char == '.':
row.append(0) #
通路
elif char == 'S':
row.append(2) #
起点
elif char == 'E':
row.append(3) #
终点
elif char == '0':
row.append(0)
elif char == '1':
row.append(1)
elif char == '2':
row.append(2)
elif char == '3':
row.append(3)
#
忽略其他字符(如制表符等)
if row:
maze.append(row)
#
统一行长度(用 0 填充)
if maze:
max_cols = max(len(row) for row in maze)
for row in maze:
while len(row) < max_cols:
row.append(0) #
用通路填充

print(f"+ 迷宫尺寸 : {len(maze)} x {len(maze0) if maze else 0} ")
return maze
def load_maze_from_json(filename: str) -> ListList\[int]:
"""
JSON 格式文件加载迷宫 """
print(f"\*
JSON 文件加载迷宫 : {filename}")
with open(filename, 'r', encoding='utf-8') as f:
data = json.load(f)
if isinstance(data, dict) and 'maze' in data:
maze = data'maze'
elif isinstance(data, list):
maze = data
else:
raise ValueError("
无法识别的 JSON 格式 ")
print(f"+
迷宫尺寸 : {len(maze)} x {len(maze0) if maze else 0} ")
return maze
def compare_algorithms(solver: MazeSolver) -> Dictstr, any:
"""
比较多种算法的性能 """
print("\n" + "="*60)
print("
算法性能比较 ")
print("="*60)
results = {}
algorithms = ('BFS', lambda: solver.bfs()), ('A\* (Manhattan)', lambda: solver.astar('manhattan')), ('A\* (Euclidean)', lambda: solver.astar('euclidean')), ('* *双向* *BFS', lambda: solver.bidirectional_bfs()),
for name, func in algorithms:
print(f"\n
测试 : {name}")
path = func()
if path:
resultsname = {
'path_length': len(path),
'nodes_explored': solver.stats'nodes_explored',
'time_taken': solver.stats'time_taken',
'max_queue_size': solver.stats'max_queue_size'
}
#
打印比较结果
print("\n" + "="*60)
print("
比较结果 ")
print("="*60)
print(f"{'
算法 ':<20} {' 路径长度 ':<10} {' 探索节点 ':<12} {' 耗时 (s)':<12} {' 队列大小 ':<10}")
print("-"*60)
for name, stats in results.items():
print(f"{name:<20} {stats'path_length':<10} {stats'nodes_explored':<12} "
f"{stats'time_taken':<12.4f} {stats'max_queue_size':<10}")
return results
def main():
"""
主函数 """
parser = argparse.ArgumentParser(
description='
迷宫最短路径求解程序 ',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''

示例 :
#
使用 BFS 求解
python standalone_path_solver.py maze.txt --algorithm bfs
#
使用 A* 求解并可视化
python standalone_path_solver.py maze.txt --algorithm astar --visualize
#
比较多种算法
python standalone_path_solver.py maze.txt --compare
#
设置自定义起点和终点
python standalone_path_solver.py maze.txt --start 0,0 --end 19,19
'''
)
parser.add_argument('maze_file', help='
迷宫文件路径 (ASCII JSON 格式 )')
parser.add_argument('--algorithm', '-a',
choices='bfs', 'astar', 'astar-manhattan', 'astar-euclidean', 'dfs', 'bidirectional-bfs', 'bidirectional',
default='bfs',
help='
选择算法 ( 默认 : bfs)')
parser.add_argument('--compare', '-c', action='store_true',
help='
比较多种算法的性能 ')
parser.add_argument('--visualize', '-v', action='store_true',
help='
生成可视化图像 ')
parser.add_argument('--output', '-o', default='path_visualization.png',
help='
可视化输出文件名 ( 默认 : path_visualization.png)')
parser.add_argument('--start', '-s', help='
起点坐标,格式 : x,y ( 例如 : 0,0)')
parser.add_argument('--end', '-e', help='
终点坐标,格式 : x,y ( 例如 : 19,19)')
parser.add_argument('--heuristic',
choices='manhattan', 'euclidean',
default='manhattan',
help='A*
启发式函数 ( 默认 : manhattan)')
parser.add_argument('--max-depth', type=int, default=10000,
help='DFS
最大搜索深度 ( 默认 : 10000)')
args = parser.parse_args()
print("="*60)
#
加载迷宫
try:
if args.maze_file.endswith('.json'):
maze = load_maze_from_json(args.maze_file)
else:
maze = load_maze_from_ascii(args.maze_file)
except Exception as e:
print(f"!
加载迷宫失败 : {e}")
sys.exit(1)
#
创建求解器
solver = MazeSolver(maze)
print(f"+
起点 : {solver.start}")
print(f"+
终点 : {solver.end}")
#
设置自定义起点和终点
if args.start:
try:
start_x, start_y = map(int, args.start.split(','))
solver.set_start_end((start_x, start_y), solver.end)
print(f"+
自定义起点 : ({start_x}, {start_y})")
except Exception as e:
print(f"!
无效的起点格式 : {e}")
sys.exit(1)
if args.end:
try:
end_x, end_y = map(int, args.end.split(','))
solver.set_start_end(solver.start, (end_x, end_y))
print(f"+
自定义终点 : ({end_x}, {end_y})")
except Exception as e:
print(f"!
无效的终点格式 : {e}")
sys.exit(1)
#
比较模式
if args.compare:
results = compare_algorithms(solver)
sys.exit(0)
#
选择算法
path = None
if args.algorithm == 'bfs':
path = solver.bfs()
elif args.algorithm == 'astar' or args.algorithm == 'astar-manhattan':
path = solver.astar('manhattan')
elif args.algorithm == 'astar-euclidean':
path = solver.astar('euclidean')
elif args.algorithm == 'dfs':
path = solver.dfs(args.max_depth)
elif args.algorithm in 'bidirectional-bfs', 'bidirectional':
path = solver.bidirectional_bfs()
#
打印结果
if path:
solver.print_stats()
solver.print_path(path)
#
可视化
if args.visualize:
solver.visualize_path(path, args.output)
#
保存路径到文件
path_file = args.maze_file.rsplit('.', 1)0 + '_path.txt'
with open(path_file, 'w', encoding='utf-8') as f:
f.write(f"#
路径求解结果 \n")
f.write(f"#
算法 : {args.algorithm}\n")
f.write(f"#
起点 : {solver.start}\n")
f.write(f"#
终点 : {solver.end}\n")
f.write(f"#
路径长度 : {len(path)} \n\n")
f.write("
路径坐标 :\n")
for i, pos in enumerate(path):
f.write(f"{i} ({pos0}, {pos1})\n")
print(f"\n+
路径已保存到 : {path_file}")
else:
print("\n-
无法找到从起点到终点的路径 ")
if name == 'main':
main()

最短路径搜素计算方法:

  1. BFS(广度优先搜索)
  • 过程:从起点开始,逐层向外扩展,记录访问标记和路径。
  1. A* 算法
  • 代价函数:f(n) = g(n) + h(n)

  • g(n):从起点到当前节点的实际步数。

  • h(n):启发函数,可选曼哈顿距离或欧氏距离。

  1. DFS(深度优先搜索)
  • 递归回溯,可设置最大深度max_depth防止无限递归。
  1. 双向BFS

-从起点和终点同时开始BFS,每次扩展一层,直到两个搜索树相遇。

相关推荐
KaMeidebaby1 小时前
卡梅德生物技术快报|细胞周期检测抗原流式分析:参数调试、软件拟合与问题排查
网络·人工智能·python·网络协议·tcp/ip·算法·机器学习
阿昭L1 小时前
Windows堆管理
windows
caimouse1 小时前
Reactos 第 3 章 内存管理 — 【下篇】换出、Section、池
c语言·开发语言·windows·架构
zmzb01031 小时前
Python课后习题训练记录Day124
开发语言·python
Linlingu1 小时前
OpenClaw对接飞书机器人完整配置教程(长连接模式)
windows·机器人·飞书·办公自动化·数字员工·小龙虾
geovindu1 小时前
python: Broadcast Pattern
开发语言·python·设计模式·广播模式
winfredzhang1 小时前
Python 实战:用 wxPython 写一个 MD5 文件查重清理工具
python·sqlite·json·wxpython·md5·预览·查重
装不满的克莱因瓶1 小时前
了解不同机器学习模型的分类
人工智能·python·算法·机器学习·ai·分类·数据挖掘
资源分享助手2 小时前
PeekDesktop:实现类似 macOS Sonoma 点击桌面预览(Windows工具教程)
windows·macos·点击回到桌面
小江的记录本2 小时前
【Spring全家桶】Spring Cloud 2023.0.x:配置中心:Nacos Config、Apollo(附《思维导图》+《面试高频考点清单》)
java·spring boot·后端·python·spring·spring cloud·面试