#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <limits.h>
#include <string.h>
#define MAX_SIZE 100
// 定义节点结构
typedef struct Node {
int x, y; // 节点坐标
int f, g, h; // A*算法的评估值(f = g + weight*h)
struct Node* parent; // 父节点指针
} Node;
// 定义开放列表和关闭列表(存储节点指针)
Node* openList[MAX_SIZE * MAX_SIZE];
Node* closeList[MAX_SIZE * MAX_SIZE];
int openSize = 0, closeSize = 0;
// 迷宫结构
char maze[MAX_SIZE][MAX_SIZE];
char mazeCopy[MAX_SIZE][MAX_SIZE]; // 用于保存原始迷宫(避免路径标记污染原始数据)
int rows, cols;
Node* start = NULL;
Node* end = NULL;
// 方向数组:上、右、下、左(仅直角移动)
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
// 函数声明
void readMaze();
Node* createNode(int x, int y);
int isValid(int x, int y);
int isObstacle(int x, int y);
int isDestination(Node* current);
void addToOpenList(Node* node);
void addToCloseList(Node* node);
Node* findInOpenList(int x, int y); // 修复:返回开放列表中的旧节点指针
int isInCloseList(int x, int y); // 优化:直接用坐标判断,避免创建临时节点
Node* popMinFromOpenList();
int manhattanDistance(Node* a, Node* b);
int euclideanDistance(Node* a, Node* b);
void tracePath(Node* node);
void aStar(int heuristic, float weight);
void freeMemory();
// 读取迷宫(同时保存原始副本)
void readMaze() {
// printf("请输入迷宫的行数和列数: ");
scanf("%d %d", &rows, &cols);
getchar(); // 消耗换行符
// printf("请输入迷宫地图(%d行,每行%d个字符,0=通路,1=障碍,S=起点,E=终点):\n", rows, cols);
for (int i = 0; i < rows; i++) {
// 读取一行并去除换行符
fgets(maze[i], MAX_SIZE, stdin);
maze[i][strcspn(maze[i], "\n")] = '\0';
// 保存原始迷宫副本(用于路径标记)
strcpy(mazeCopy[i], maze[i]);
// 查找起点和终点
for (int j = 0; j < cols; j++) {
if (maze[i][j] == 'S') {
start = createNode(i, j);
} else if (maze[i][j] == 'E') {
end = createNode(i, j);
}
}
}
}
// 创建新节点
Node* createNode(int x, int y) {
Node* node = (Node*)malloc(sizeof(Node));
if (node == NULL) {
printf("内存分配失败!\n");
exit(1);
}
node->x = x;
node->y = y;
node->f = 0;
node->g = 0;
node->h = 0;
node->parent = NULL;
return node;
}
// 检查坐标是否在迷宫内
int isValid(int x, int y) {
return (x >= 0 && x < rows && y >= 0 && y < cols);
}
// 检查是否是障碍物
int isObstacle(int x, int y) {
return maze[x][y] == '1';
}
// 检查是否是目标节点
int isDestination(Node* current) {
return (current->x == end->x && current->y == end->y);
}
// 添加节点到开放列表
void addToOpenList(Node* node) {
if (openSize >= MAX_SIZE * MAX_SIZE) {
printf("开放列表已满!\n");
return;
}
openList[openSize++] = node;
}
// 添加节点到关闭列表
void addToCloseList(Node* node) {
if (closeSize >= MAX_SIZE * MAX_SIZE) {
printf("关闭列表已满!\n");
return;
}
closeList[closeSize++] = node;
}
// 在开放列表中查找指定坐标的节点(返回节点指针,未找到返回NULL)
Node* findInOpenList(int x, int y) {
for (int i = 0; i < openSize; i++) {
if (openList[i]->x == x && openList[i]->y == y) {
return openList[i];
}
}
return NULL;
}
// 检查指定坐标的节点是否在关闭列表中(返回1=存在,0=不存在)
int isInCloseList(int x, int y) {
for (int i = 0; i < closeSize; i++) {
if (closeList[i]->x == x && closeList[i]->y == y) {
return 1;
}
}
return 0;
}
// 从开放列表中弹出f值最小的节点(核心:维护A*的优先级)
Node* popMinFromOpenList() {
if (openSize == 0) {
return NULL;
}
// 找到f值最小的节点索引
int minIndex = 0;
for (int i = 1; i < openSize; i++) {
if (openList[i]->f < openList[minIndex]->f) {
minIndex = i;
}
}
// 保存最小f值节点,并从开放列表中移除
Node* minNode = openList[minIndex];
for (int i = minIndex; i < openSize - 1; i++) {
openList[i] = openList[i + 1]; // 前移覆盖
}
openSize--;
return minNode;
}
// 启发函数1:曼哈顿距离(适合直角移动,可采纳性强)
int manhattanDistance(Node* a, Node* b) {
return abs(a->x - b->x) + abs(a->y - b->y);
}
// 启发函数2:欧几里得距离(适合允许对角线移动,此处作对比)
int euclideanDistance(Node* a, Node* b) {
int dx = a->x - b->x;
int dy = a->y - b->y;
return (int)sqrt(dx * dx + dy * dy); // 转换为整数便于f值计算
}
// 追踪并显示路径(使用迷宫副本,避免污染原始输入)
void tracePath(Node* node) {
if (node == NULL) {
return;
}
int pathLength = 0;
Node* temp = node;
// 1. 回溯路径,统计长度(从终点到起点)
while (temp != NULL) {
pathLength++;
temp = temp->parent;
}
pathLength--; // 减去起点本身(路径长度=步数)
// 2. 标记路径(用'*'标记,跳过起点S和终点E)
temp = node;
while (temp != NULL) {
if (mazeCopy[temp->x][temp->y] != 'S' && mazeCopy[temp->x][temp->y] != 'E') {
mazeCopy[temp->x][temp->y] = '*';
}
temp = temp->parent;
}
// 3. 打印带路径的迷宫
printf("\n===== 路径结果 =====");
printf("\n路径长度: %d 步", pathLength);
printf("\n带路径的迷宫:\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%c ", mazeCopy[i][j]);
}
printf("\n");
}
}
//请在此处添加代码完成任务
//##################Begin##################
// A*算法核心实现(支持标准A*和加权A*,可切换启发函数)
void aStar(int heuristic, float weight) {
// 初始化列表(避免多次调用时残留数据)
openSize = 0;
closeSize = 0;
memset(openList, 0, sizeof(openList));
memset(closeList, 0, sizeof(closeList));
// 初始化起点:g=0(起点到自身代价为0)
int expandedNodes = 0; // 统计扩展节点数(搜索效率指标)
while (openSize > 0) {
// 1. 取出开放列表中f值最小的节点(A*核心逻辑)
// 2. 检查是否到达终点:到达则追踪路径并返回
// 3. 将当前节点加入关闭列表(标记为已扩展)
// 4. 遍历四个方向的邻居节点
// 5. 计算邻居节点的临时g值(当前g+1,每步代价为1)
}
// 开放列表为空仍未找到终点 → 无路径
printf("\n未找到路径!\n");
}
//###################End###################
// 释放所有动态分配的内存(避免内存泄漏)
void freeMemory() {
// 释放开放列表中的节点
for (int i = 0; i < openSize; i++) {
if (openList[i] != NULL) {
free(openList[i]);
openList[i] = NULL;
}
}
// 释放关闭列表中的节点(start和end已在列表中,无需单独释放)
for (int i = 0; i < closeSize; i++) {
if (closeList[i] != NULL) {
free(closeList[i]);
closeList[i] = NULL;
}
}
// 重置指针(避免野指针)
start = NULL;
end = NULL;
}
int main() {
int heuristic;
float weight;
// 1. 读取迷宫输入
readMaze();
// 2. 选择算法参数(启发函数+权重)
// printf("\n===== 算法参数设置 =====");
do {
// printf("\n选择启发函数 (1:曼哈顿距离, 2:欧几里得距离): ");
scanf("%d", &heuristic);
} while (heuristic != 1 && heuristic != 2); // 输入校验
// printf("输入权重w (标准A*算法输入1.0,加权A*输入>1.0的值): ");
scanf("%f", &weight);
if (weight <= 0) {
printf("权重无效,自动设置为1.0(标准A*)\n");
weight = 1.0;
}
// 3. 执行A*算法并计时
printf("\n===== 算法执行 =====");
clock_t startTime = clock();
aStar(heuristic, weight);
clock_t endTime = clock();
double runTime = (double)(endTime - startTime) / CLOCKS_PER_SEC;
// 4. 输出运行时间
printf("运行时间: %.4f 秒\n", runTime);
// 5. 释放内存(避免内存泄漏)
freeMemory();
return 0;
}任务描述
迷宫路径规划是人工智能领域中路径搜索的经典问题。本任务要求使用 A * 算法及其变体实现迷宫中从起点到终点的路径规划,并通过不同参数设置分析算法性能。
具体任务:
实现标准 A * 算法,使用两种不同的启发函数(曼哈顿距离和欧几里得距离)
实现加权 A算法(Weighted A),通过调整权重参数 w 分析其影响
对比不同算法和参数设置下的路径规划结果,包括路径长度、搜索效率和解的质量
迷宫表示为二维网格,其中:
0 表示可通行区域
1 表示障碍物
S 表示起点
E表示终点
相关知识
为了完成本关任务,你需要掌握:
A * 算法原理
启发函数
加权 A * 算法
数据结构
A * 算法原理
A* 搜索算法(A* search algorithm,A* 读作 A-star),简称 A* 算法,是一种在带权有向图上,找到给定起点与终点之间的最短路径的算法。它属于图遍历(graph traversal)和最佳优先搜索算法(best-first search),亦是 BFS 的改进。
过程
A* 算法的目标是找到有向图上从起点s到终点t的最短路径。设d(x,y)为结点x与y之间的距离,也就是它们之间最短路径的长度。
记g(x)=d(s,x)为从起点s到结点x的距离函数,h∗(x)为从结点x到终点t的距离函数,h(x)为h∗(x)的一个估计。最后,记从s出发经由x到达t的最短路径长度的估计为
f(x)=g(x)+h(x)
搜索时,A* 算法每次从优先队列中取出一个f最小的结点。然后,将它的所有后继结点x都推入优先队列中,并利用实际记录的g(x)和估计的h(x)更新f(x)。
#伪代码:A*(A-Star)启发式搜索算法
# 目标:在一个二维迷宫中,从起点 start 寻找到终点 end 的最短路径
# 说明:A* 是一种结合了“实际代价(g)”和“预估代价(h)”的最优搜索算法
# 启发函数 h 使用“曼哈顿距离”(适合四向移动的格子地图)
astar(maze, start, end) {
# 启发函数:返回两点间的曼哈顿距离
function heuristic(a, b) {
return abs(a.x - b.x) + abs(a.y - b.y)
}
# 四个基本移动方向:上、下、左、右
dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)]
# 初始化阶段
open_heap = new min_heap() # 优先队列(最小堆),按 f 值排序
open_heap.push((0, start)) # 起点的 f = 0
came_from[start] = NULL # 记录每个节点的前驱,用于回溯路径
g_score[start] = 0 # 起点到自身的代价为 0
# 主循环:当开放列表不为空时持续搜索
while (!open_heap.empty()) {
(f, current) = open_heap.pop_min() # 取出 f 值最小的节点
# 若当前节点为终点,则回溯路径并返回
if (current == end) {
path = new list()
while (current != NULL) {
path.push_front(current) # 从终点反向压入路径
current = came_from[current] # 沿前驱回溯
}
return path # 返回从起点到终点的完整路径
}
# 遍历当前节点的所有邻居节点
for each (dx, dy) in dirs {
neighbor = (current.x + dx, current.y + dy)
# 检查邻居是否在迷宫范围内,且可通行(0 表示可走,1 表示障碍)
if (0 <= neighbor.x < maze.rows and
0 <= neighbor.y < maze.cols and
maze[neighbor.x][neighbor.y] == 0) {
new_g = g_score[current] + 1 # 当前节点代价 + 1(每步代价固定为1)
# 若邻居未访问过,或发现更优路径(代价更小)
if (neighbor not in g_score or new_g < g_score[neighbor]) {
g_score[neighbor] = new_g
f_score = new_g + heuristic(neighbor, end) # f = g + h
open_heap.push((f_score, neighbor)) # 将邻居加入开放列表
came_from[neighbor] = current # 记录前驱节点
}
}
}
}
# 若开放列表耗尽仍未到达终点,则无可行路径
return NULL
}
变量说明:
maze[x][y] —— 二维迷宫矩阵,0 表示可通行,1 表示障碍
start, end —— 起点与终点坐标(x, y)
dirs[] —— 四个移动方向
open_heap —— 按 f 值(f = g + h)排序的最小堆(优先队列)
g_score[node] —— 起点到该节点的实际代价
heuristic(a, b) —— 启发式估计函数(预估从 a 到 b 的距离)
came_from[node] —— 每个节点的前驱,用于回溯路径
核心思想:
A* = Dijkstra + 启发式搜索
g(n):起点到当前节点 n 的实际代价
h(n):从节点 n 到目标节点的估计代价
f(n) = g(n) + h(n)
算法每次从 open_heap 中选取 f 最小的节点扩展,从而高效地逼近目标
启发函数
启发函数用于估计当前节点到目标节点的代价,引导搜索算法优先探索更可能接近目标的路径,提高搜索效率。
曼哈顿距离:h(n)=∣x1−x2∣+∣y1−y2∣(适合网格中只能直角移动的情况)
欧几里得距离:h(n)=√[(x1−x2)²+(y1−y2)²](适合允许对角线移动的情况)
启发函数需满足可采纳性(不会高估实际代价)以保证 A * 算法找到最优解
加权 A * 算法
评估函数:f(n)=g(n)+w・h(n),其中 w 是权重参数
当 w>1 时,算法更依赖启发信息,可能找到次优解但搜索速度更快
当 w=1 时,即为标准 A * 算法
当 0<w<1 时,算法更接近 Dijkstra 算法
数据结构
开放列表(Open List):存储待扩展的节点,通常用优先队列实现
封闭列表(Closed List):存储已扩展的节点,用于避免重复搜索
编程要求
需补充的代码位置起始行数为:218
用 C 语言实现标准 A算法和加权 A算法
实现两种启发函数:曼哈顿距离和欧几里得距离
程序需接收迷宫数据作为输入,输出以下信息:
是否找到路径
路径长度(步数)
扩展节点数量(搜索效率指标)
路径的可视化表示
算法运行时间(可选)
允许用户设置加权 A * 算法的权重参数 w
对比不同算法和参数设置的性能差异
测试说明
平台会对你编写的代码进行测试:
测试输入:
迷宫的行数和列数:5 5
迷宫地图:
S0000
11110
00000
01111
0000E
选择启发函数 (1:曼哈顿距离, 2:欧几里得距离):1
输入权重w (标准A算法输入1.0,加权A输入>1.0的值): 1.0
预期输出:
===== 算法执行 =====
扩展节点数: 17
===== 路径结果 =====
路径长度: 16 步
带路径的迷宫:
S * * * *
1 1 1 1 *
* * * * *
* 1 1 1 1
* * * * E
运行时间: 0.0001 秒