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: List[List[int]]):
self.maze = maze_data
self.rows = len(maze_data)
self.cols = len(maze_data[0]) 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) -> Optional[Tuple[int, int]]:
"""
查找指定值的单元格 """
for y in range(self.rows):
for x in range(self.cols):
if self.maze[y][x] == 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.maze[y][x] != 1
def set_start_end(self, start: Tuple[int, int], end: Tuple[int, 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) -> Optional[List[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 = current[0] + dx, current[1] + 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') -> Optional[List[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 visited[current] < 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 = current[0] + dx, current[1] + 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')):
visited[neighbor] = 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: Tuple[int, int], method: str) -> float:
"""
计算启发式函数值 """
if method == 'manhattan':
return abs(pos[0] - self.end[0]) + abs(pos[1] - self.end[1])
elif method == 'euclidean':
return ((pos[0] - self.end[0]) ** 2 + (pos[1] - self.end[1]) ** 2) ** 0.5
else:
return 0
def dfs(self, max_depth: int = 10000) -> Optional[List[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: Tuple[int, int], path: List[Tuple[int, int]], visited: Set) -> Optional[List[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 = current[0] + dx, current[1] + 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) -> Optional[List[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_visited[current]
#
注意: other_path 需要反转(如果是从终点搜索的)
if queue == queue_end:
other_path = other_path[::-1]
return path + other_path[1:]
for dx, dy in directions:
nx, ny = current[0] + dx, current[1] + dy
neighbor = (nx, ny)
if self.is_passable(nx, ny) and neighbor not in visited:
visited[neighbor] = 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: List[Tuple[int, int]]):
"""
打印路径 """
if not path:
print("\n[-]
无路径可显示 ")
return
print("\n" + "-"*60)
print("
路径详情 :")
print("-"*60)
print(f"
起点 : {path[0]}")
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(path[i-1] if i > 0 else None, pos)
print(f"
步骤 {i:3d}: ({pos[0]:3d}, {pos[1]:3d}) {direction}")
#
计算方向统计
print("\n 方向统计 :")
direction_counts = {'
': 0, ' ': 0, ' ': 0, ' ': 0}
for i in range(len(path) - 1):
direction = self._get_direction(path[i], path[i+1], chinese=False)
if direction:
direction_counts[direction] += 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: Optional[Tuple[int, int]],
to_pos: Tuple[int, int], chinese: bool = True) -> str:
"""
获取移动方向 """
if from_pos is None:
return "
起点 " if chinese else "Start"
dx = to_pos[0] - from_pos[0]
dy = to_pos[1] - from_pos[1]
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: List[Tuple[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.maze[y][x] == 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 = path[i]
x2, y2 = path[i + 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 = path[0]
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) -> List[List[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(maze[0]) if maze else 0} ")
return maze
def load_maze_from_json(filename: str) -> List[List[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(maze[0]) if maze else 0} ")
return maze
def compare_algorithms(solver: MazeSolver) -> Dict[str, 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:
results[name] = {
'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} ({pos[0]}, {pos[1]})\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,每次扩展一层,直到两个搜索树相遇。

相关推荐
尘埃落定wf2 小时前
FastAPI 鉴权怎么写?中间件和依赖注入一次说清楚
python·中间件·fastapi
2301_773553622 小时前
构建 Go CLI 应用的最佳实践:纯 Go 交互式命令行库选型与使用指南
jvm·数据库·python
qq_372906932 小时前
c#如何添加按钮点击事件_c#添加按钮点击事件的几种常见用法
jvm·数据库·python
2301_817672262 小时前
JavaScript 中高效定位二维数组间不匹配元素的行列索引
jvm·数据库·python
2401_831419442 小时前
golang如何实现验证码图片生成_golang验证码图片生成实现实战
jvm·数据库·python
谪星·阿凯2 小时前
电商系统Web渗透测试实战指南
前端·网络·安全·web安全·网络安全
LiAo_1996_Y2 小时前
CSS实现多列等高布局_浮动布局的高级处理技巧
jvm·数据库·python
Jenlybein2 小时前
用 uv 替代 conda,速度飙升(从 0 到 1 开始使用 uv)
后端·python·算法
一个人旅程~2 小时前
ARM版的windows(macbook虚拟机使用)在国内外技术平台有哪些版本可以选择?
windows·经验分享·macos·电脑