
目录
- [C++ A* 算法:启发式路径搜索的黄金标准](#C++ A* 算法:启发式路径搜索的黄金标准)
-
- 引言
- [一、A* 算法核心原理](#一、A* 算法核心原理)
-
- [1. 核心概念定义](#1. 核心概念定义)
- [2. 启发函数(Heuristic Function)](#2. 启发函数(Heuristic Function))
- [二、A* 算法 C++ 实现基础](#二、A* 算法 C++ 实现基础)
-
- [1. 核心数据结构定义](#1. 核心数据结构定义)
- [2. 辅助函数实现](#2. 辅助函数实现)
- [3. A* 算法核心实现](#3. A* 算法核心实现)
- [三、C++ 实战:A* 网格寻路完整代码](#三、C++ 实战:A* 网格寻路完整代码)
- [四、A* 算法关键优化技巧](#四、A* 算法关键优化技巧)
-
- [1. 启发函数优化](#1. 启发函数优化)
- [2. 数据结构优化](#2. 数据结构优化)
- [3. 搜索剪枝](#3. 搜索剪枝)
- [4. 路径平滑](#4. 路径平滑)
- 五、常见坑点与避坑指南
- [六、A* vs 其他路径算法(核心差异)](#六、A* vs 其他路径算法(核心差异))
- 七、总结
C++ A* 算法:启发式路径搜索的黄金标准
引言
A*(A-Star)算法是最经典的启发式路径搜索算法,结合了 Dijkstra 算法的"全局最优"和贪心算法的"启发式引导",在路径规划(如游戏寻路、机器人导航、地图导航)中应用广泛。A* 的核心是通过"预估代价 + 实际代价"的评估函数,优先搜索最有可能通向目标的路径,大幅减少无效搜索,效率远高于传统的 DFS/BFS/Dijkstra。本文将从核心原理、数据结构、实现框架到 C++ 实战(网格寻路),帮你彻底掌握 A* 算法。
一、A* 算法核心原理
1. 核心概念定义
在讲解算法前,先明确 A* 的关键术语(以网格寻路为例):
| 术语 | 含义 | 计算公式 |
|---|---|---|
| g ( n ) g(n) g(n) | 从起点到节点 n n n 的实际代价 | 如网格中相邻节点的移动成本(直走10,斜走14) |
| h ( n ) h(n) h(n) | 从节点 n n n 到目标节点的预估代价(启发函数) | 曼哈顿距离/欧几里得距离/切比雪夫距离 |
| f ( n ) f(n) f(n) | 节点 n n n 的总评估代价 | f ( n ) = g ( n ) + h ( n ) f(n) = g(n) + h(n) f(n)=g(n)+h(n) |
| 开放列表(Open List) | 待探索的节点集合 | 优先队列(按 f ( n ) f(n) f(n) 升序) |
| 关闭列表(Closed List) | 已探索的节点集合 | 哈希表/二维数组(快速查询) |
2. 启发函数(Heuristic Function)
启发函数是 A* 的灵魂,需满足 可采纳性 ( h ( n ) ≤ h(n) \leq h(n)≤ 实际代价),保证找到最优解:
- 曼哈顿距离 (适合网格仅允许上下左右移动):
h ( n ) = 10 × ( ∣ x n − x g o a l ∣ + ∣ y n − y g o a l ∣ ) h(n) = 10 \times (|x_n - x_{goal}| + |y_n - y_{goal}|) h(n)=10×(∣xn−xgoal∣+∣yn−ygoal∣) - 欧几里得距离 (适合网格允许任意方向移动):
h ( n ) = 10 × ( x n − x g o a l ) 2 + ( y n − y g o a l ) 2 h(n) = 10 \times \sqrt{(x_n - x_{goal})^2 + (y_n - y_{goal})^2} h(n)=10×(xn−xgoal)2+(yn−ygoal)2 - 切比雪夫距离 (适合网格允许斜向移动):
h ( n ) = 10 × max ( ∣ x n − x g o a l ∣ , ∣ y n − y g o a l ∣ ) h(n) = 10 \times \max(|x_n - x_{goal}|, |y_n - y_{goal}|) h(n)=10×max(∣xn−xgoal∣,∣yn−ygoal∣)
二、A* 算法 C++ 实现基础
1. 核心数据结构定义
cpp
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_set>
#include <cmath>
#include <algorithm>
#include <climits>
using namespace std;
// ===================== 核心常量与类型定义 =====================
// 网格移动成本(直走10,斜走14,简化计算避免浮点数)
const int MOVE_STRAIGHT_COST = 10;
const int MOVE_DIAGONAL_COST = 14;
// 节点状态
struct Node {
int x, y; // 网格坐标
int g_cost; // 起点到当前节点的实际代价
int h_cost; // 到目标节点的预估代价
int f_cost; // 总代价 = g + h
Node* parent; // 父节点(用于回溯路径)
bool is_obstacle; // 是否是障碍物
Node(int x_, int y_) : x(x_), y(y_), g_cost(INT_MAX), h_cost(0),
f_cost(0), parent(nullptr), is_obstacle(false) {}
// 计算总代价
void calculate_f_cost() {
f_cost = g_cost + h_cost;
}
};
// 网格地图
using Grid = vector<vector<Node*>>;
// 优先队列的比较规则(f_cost 升序,f相同则h升序)
struct CompareNode {
bool operator()(Node* a, Node* b) {
if (a->f_cost != b->f_cost) {
return a->f_cost > b->f_cost; // 小顶堆
}
return a->h_cost > b->h_cost;
}
};
// 开放列表:优先队列(按f_cost排序)
using OpenList = priority_queue<Node*, vector<Node*>, CompareNode>;
// 关闭列表:哈希集合(快速查询)
using ClosedList = unordered_set<int>;
// 坐标哈希函数(用于ClosedList)
struct CoordHash {
size_t operator()(const pair<int, int>& coord) const {
return coord.first * 1000 + coord.second; // 假设网格大小不超过1000x1000
}
};
2. 辅助函数实现
cpp
// ===================== 辅助函数 =====================
// 计算曼哈顿距离(仅上下左右移动)
int calculate_manhattan_distance(Node* a, Node* b) {
int dx = abs(a->x - b->x);
int dy = abs(a->y - b->y);
return MOVE_STRAIGHT_COST * (dx + dy);
}
// 计算切比雪夫距离(允许斜向移动)
int calculate_chebyshev_distance(Node* a, Node* b) {
int dx = abs(a->x - b->x);
int dy = abs(a->y - b->y);
return MOVE_STRAIGHT_COST * max(dx, dy);
}
// 获取节点的哈希键(x*1000 + y)
int get_node_key(Node* node) {
return node->x * 1000 + node->y;
}
// 获取相邻节点(8方向,可根据需求改为4方向)
vector<Node*> get_neighbors(Node* node, Grid& grid) {
vector<Node*> neighbors;
int rows = grid.size();
if (rows == 0) return neighbors;
int cols = grid[0].size();
// 8个方向的偏移量
vector<pair<int, int>> dirs = {
{-1, 0}, {1, 0}, {0, -1}, {0, 1}, // 上下左右
{-1, -1}, {-1, 1}, {1, -1}, {1, 1} // 斜向
};
for (auto& dir : dirs) {
int nx = node->x + dir.first;
int ny = node->y + dir.second;
// 检查坐标合法性
if (nx >= 0 && nx < rows && ny >= 0 && ny < cols) {
Node* neighbor = grid[nx][ny];
// 跳过障碍物
if (!neighbor->is_obstacle) {
neighbors.push_back(neighbor);
}
}
}
return neighbors;
}
// 回溯路径(从目标节点到起点)
vector<pair<int, int>> reconstruct_path(Node* goal_node) {
vector<pair<int, int>> path;
Node* current = goal_node;
// 从目标节点回溯到起点
while (current != nullptr) {
path.emplace_back(current->x, current->y);
current = current->parent;
}
// 反转路径(起点→目标)
reverse(path.begin(), path.end());
return path;
}
// 初始化网格(创建节点,设置障碍物)
Grid init_grid(int rows, int cols, const vector<pair<int, int>>& obstacles) {
Grid grid;
for (int i = 0; i < rows; ++i) {
vector<Node*> row;
for (int j = 0; j < cols; ++j) {
row.push_back(new Node(i, j));
}
grid.push_back(row);
}
// 设置障碍物
for (auto& obs : obstacles) {
int x = obs.first;
int y = obs.second;
if (x >= 0 && x < rows && y >= 0 && y < cols) {
grid[x][y]->is_obstacle = true;
}
}
return grid;
}
// 释放网格内存
void free_grid(Grid& grid) {
for (auto& row : grid) {
for (auto& node : row) {
delete node;
}
}
grid.clear();
}
// 打印路径
void print_path(const vector<pair<int, int>>& path) {
if (path.empty()) {
cout << "无可行路径!" << endl;
return;
}
cout << "路径(共" << path.size() << "步):" << endl;
for (int i = 0; i < path.size(); ++i) {
cout << "(" << path[i].first << "," << path[i].second << ")";
if (i != path.size() - 1) {
cout << " → ";
}
}
cout << endl;
}
// 打印网格(可视化路径和障碍物)
void print_grid(Grid& grid, const vector<pair<int, int>>& path) {
int rows = grid.size();
int cols = grid[0].size();
// 将路径存入哈希集合,方便查询
unordered_set<int, CoordHash> path_set;
for (auto& p : path) {
path_set.insert({p.first, p.second});
}
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
if (grid[i][j]->is_obstacle) {
cout << "■ "; // 障碍物
} else if (path_set.count({i, j})) {
cout << "● "; // 路径
} else {
cout << "□ "; // 空网格
}
}
cout << endl;
}
}
3. A* 算法核心实现
cpp
// ===================== A* 算法核心函数 =====================
vector<pair<int, int>> a_star_search(
Grid& grid,
pair<int, int> start_coord,
pair<int, int> goal_coord,
bool allow_diagonal = true // 是否允许斜向移动
) {
// 1. 获取起点和目标节点
Node* start_node = grid[start_coord.first][start_coord.second];
Node* goal_node = grid[goal_coord.first][goal_coord.second];
// 边界检查:起点/目标是障碍物
if (start_node->is_obstacle || goal_node->is_obstacle) {
cout << "起点或目标是障碍物!" << endl;
return {};
}
// 2. 初始化开放列表和关闭列表
OpenList open_list;
ClosedList closed_list;
// 3. 初始化起点
start_node->g_cost = 0;
if (allow_diagonal) {
start_node->h_cost = calculate_chebyshev_distance(start_node, goal_node);
} else {
start_node->h_cost = calculate_manhattan_distance(start_node, goal_node);
}
start_node->calculate_f_cost();
open_list.push(start_node);
// 4. 核心搜索循环
while (!open_list.empty()) {
// 取出f_cost最小的节点
Node* current_node = open_list.top();
open_list.pop();
// 如果是目标节点,回溯路径
if (current_node == goal_node) {
return reconstruct_path(goal_node);
}
// 将当前节点加入关闭列表
closed_list.insert(get_node_key(current_node));
// 遍历相邻节点
vector<Node*> neighbors = get_neighbors(current_node, grid);
for (Node* neighbor : neighbors) {
// 跳过关闭列表中的节点
if (closed_list.count(get_node_key(neighbor))) {
continue;
}
// 计算从当前节点到相邻节点的代价
int new_g_cost = current_node->g_cost;
// 判断是否是斜向移动
bool is_diagonal = (abs(current_node->x - neighbor->x) == 1) &&
(abs(current_node->y - neighbor->y) == 1);
if (is_diagonal) {
if (!allow_diagonal) continue; // 不允许斜向则跳过
new_g_cost += MOVE_DIAGONAL_COST;
} else {
new_g_cost += MOVE_STRAIGHT_COST;
}
// 如果新路径更优,或节点不在开放列表中
if (new_g_cost < neighbor->g_cost) {
neighbor->parent = current_node;
neighbor->g_cost = new_g_cost;
// 更新h_cost
if (allow_diagonal) {
neighbor->h_cost = calculate_chebyshev_distance(neighbor, goal_node);
} else {
neighbor->h_cost = calculate_manhattan_distance(neighbor, goal_node);
}
neighbor->calculate_f_cost();
// 如果节点不在开放列表中,加入
// 注意:优先队列无法直接判断,这里简化处理(允许重复入队,关闭列表去重)
open_list.push(neighbor);
}
}
}
// 开放列表为空,无路径
return {};
}
三、C++ 实战:A* 网格寻路完整代码
cpp
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_set>
#include <cmath>
#include <algorithm>
#include <climits>
#include <utility>
using namespace std;
// 核心常量
const int MOVE_STRAIGHT_COST = 10;
const int MOVE_DIAGONAL_COST = 14;
// 节点结构体
struct Node {
int x, y;
int g_cost;
int h_cost;
int f_cost;
Node* parent;
bool is_obstacle;
Node(int x_, int y_) : x(x_), y(y_), g_cost(INT_MAX), h_cost(0),
f_cost(0), parent(nullptr), is_obstacle(false) {}
void calculate_f_cost() {
f_cost = g_cost + h_cost;
}
};
// 网格类型
using Grid = vector<vector<Node*>>;
// 优先队列比较规则
struct CompareNode {
bool operator()(Node* a, Node* b) {
if (a->f_cost != b->f_cost) {
return a->f_cost > b->f_cost;
}
return a->h_cost > b->h_cost;
}
};
// 开放列表和关闭列表
using OpenList = priority_queue<Node*, vector<Node*>, CompareNode>;
using ClosedList = unordered_set<int>;
// 辅助函数:计算曼哈顿距离
int calculate_manhattan_distance(Node* a, Node* b) {
int dx = abs(a->x - b->x);
int dy = abs(a->y - b->y);
return MOVE_STRAIGHT_COST * (dx + dy);
}
// 辅助函数:计算切比雪夫距离
int calculate_chebyshev_distance(Node* a, Node* b) {
int dx = abs(a->x - b->x);
int dy = abs(a->y - b->y);
return MOVE_STRAIGHT_COST * max(dx, dy);
}
// 辅助函数:获取节点哈希键
int get_node_key(Node* node) {
return node->x * 1000 + node->y;
}
// 辅助函数:获取相邻节点
vector<Node*> get_neighbors(Node* node, Grid& grid) {
vector<Node*> neighbors;
int rows = grid.size();
if (rows == 0) return neighbors;
int cols = grid[0].size();
vector<pair<int, int>> dirs = {
{-1, 0}, {1, 0}, {0, -1}, {0, 1},
{-1, -1}, {-1, 1}, {1, -1}, {1, 1}
};
for (auto& dir : dirs) {
int nx = node->x + dir.first;
int ny = node->y + dir.second;
if (nx >= 0 && nx < rows && ny >= 0 && ny < cols) {
Node* neighbor = grid[nx][ny];
if (!neighbor->is_obstacle) {
neighbors.push_back(neighbor);
}
}
}
return neighbors;
}
// 辅助函数:回溯路径
vector<pair<int, int>> reconstruct_path(Node* goal_node) {
vector<pair<int, int>> path;
Node* current = goal_node;
while (current != nullptr) {
path.emplace_back(current->x, current->y);
current = current->parent;
}
reverse(path.begin(), path.end());
return path;
}
// 辅助函数:初始化网格
Grid init_grid(int rows, int cols, const vector<pair<int, int>>& obstacles) {
Grid grid;
for (int i = 0; i < rows; ++i) {
vector<Node*> row;
for (int j = 0; j < cols; ++j) {
row.push_back(new Node(i, j));
}
grid.push_back(row);
}
for (auto& obs : obstacles) {
int x = obs.first;
int y = obs.second;
if (x >= 0 && x < rows && y >= 0 && y < cols) {
grid[x][y]->is_obstacle = true;
}
}
return grid;
}
// 辅助函数:释放网格内存
void free_grid(Grid& grid) {
for (auto& row : grid) {
for (auto& node : row) {
delete node;
}
}
grid.clear();
}
// 辅助函数:打印路径
void print_path(const vector<pair<int, int>>& path) {
if (path.empty()) {
cout << "无可行路径!" << endl;
return;
}
cout << "\n最优路径(步数:" << path.size() << "):" << endl;
for (int i = 0; i < path.size(); ++i) {
cout << "(" << path[i].first << "," << path[i].second << ")";
if (i != path.size() - 1) {
cout << " → ";
}
}
cout << endl;
}
// 辅助函数:打印网格
void print_grid(Grid& grid, const vector<pair<int, int>>& path) {
int rows = grid.size();
int cols = grid[0].size();
unordered_set<int> path_set;
for (auto& p : path) {
path_set.insert(p.first * 1000 + p.second);
}
cout << "\n网格可视化(■=障碍物,●=路径,□=空):" << endl;
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
if (grid[i][j]->is_obstacle) {
cout << "■ ";
} else if (path_set.count(i * 1000 + j)) {
cout << "● ";
} else {
cout << "□ ";
}
}
cout << endl;
}
}
// A* 核心搜索函数
vector<pair<int, int>> a_star_search(
Grid& grid,
pair<int, int> start_coord,
pair<int, int> goal_coord,
bool allow_diagonal = true
) {
Node* start_node = grid[start_coord.first][start_coord.second];
Node* goal_node = grid[goal_coord.first][goal_coord.second];
if (start_node->is_obstacle || goal_node->is_obstacle) {
cout << "错误:起点或目标是障碍物!" << endl;
return {};
}
OpenList open_list;
ClosedList closed_list;
// 初始化起点
start_node->g_cost = 0;
if (allow_diagonal) {
start_node->h_cost = calculate_chebyshev_distance(start_node, goal_node);
} else {
start_node->h_cost = calculate_manhattan_distance(start_node, goal_node);
}
start_node->calculate_f_cost();
open_list.push(start_node);
// 核心循环
while (!open_list.empty()) {
Node* current_node = open_list.top();
open_list.pop();
if (current_node == goal_node) {
return reconstruct_path(goal_node);
}
closed_list.insert(get_node_key(current_node));
vector<Node*> neighbors = get_neighbors(current_node, grid);
for (Node* neighbor : neighbors) {
if (closed_list.count(get_node_key(neighbor))) {
continue;
}
int new_g_cost = current_node->g_cost;
bool is_diagonal = (abs(current_node->x - neighbor->x) == 1) &&
(abs(current_node->y - neighbor->y) == 1);
if (is_diagonal && !allow_diagonal) {
continue;
}
new_g_cost += is_diagonal ? MOVE_DIAGONAL_COST : MOVE_STRAIGHT_COST;
if (new_g_cost < neighbor->g_cost) {
neighbor->parent = current_node;
neighbor->g_cost = new_g_cost;
if (allow_diagonal) {
neighbor->h_cost = calculate_chebyshev_distance(neighbor, goal_node);
} else {
neighbor->h_cost = calculate_manhattan_distance(neighbor, goal_node);
}
neighbor->calculate_f_cost();
open_list.push(neighbor);
}
}
}
return {};
}
// 主函数:测试A*算法
int main() {
// 1. 配置网格(10x10)
int rows = 10;
int cols = 10;
// 2. 设置障碍物
vector<pair<int, int>> obstacles = {
{2, 2}, {2, 3}, {2, 4}, {2, 5}, // 竖墙
{5, 1}, {5, 2}, {5, 3}, {5, 4}, // 横墙
{7, 6}, {7, 7}, {7, 8} // 斜墙
};
// 3. 初始化网格
Grid grid = init_grid(rows, cols, obstacles);
// 4. 设置起点和目标
pair<int, int> start = {0, 0};
pair<int, int> goal = {9, 9};
// 5. 执行A*搜索(允许斜向移动)
vector<pair<int, int>> path = a_star_search(grid, start, goal, true);
// 6. 打印结果
print_path(path);
print_grid(grid, path);
// 7. 释放内存
free_grid(grid);
return 0;
}
四、A* 算法关键优化技巧
1. 启发函数优化
- 可采纳性优先 :保证 h ( n ) ≤ h(n) \leq h(n)≤ 实际代价,确保最优解;
- 动态加权 :搜索初期增大 h ( n ) h(n) h(n) 权重(如 f ( n ) = g ( n ) + 2 ∗ h ( n ) f(n) = g(n) + 2*h(n) f(n)=g(n)+2∗h(n)),加快搜索;后期恢复为 f ( n ) = g ( n ) + h ( n ) f(n) = g(n) + h(n) f(n)=g(n)+h(n),保证最优;
- 预处理启发函数:如使用 Dijkstra 预计算所有节点到目标的距离(适用于静态地图)。
2. 数据结构优化
- 开放列表优化 :使用
unordered_map记录开放列表中的节点,避免重复入队(核心优化,减少无效计算); - 关闭列表优化 :用二维布尔数组替代哈希集合,查询效率从 O ( 1 ) O(1) O(1)(哈希)提升到 O ( 1 ) O(1) O(1)(数组,无哈希冲突);
- 节点池化 :预先分配所有节点内存,避免频繁
new/delete。
3. 搜索剪枝
- 边界剪枝:跳过超出地图范围的节点;
- 障碍物剪枝:直接跳过障碍物节点;
- 方向剪枝:若不允许斜向移动,直接跳过斜向邻居。
4. 路径平滑
- 移除冗余节点:如连续三个节点在同一直线上,可删除中间节点;
- 折线优化:将"直角"路径优化为斜向路径(若允许)。
五、常见坑点与避坑指南
-
启发函数不可采纳:
- 坑: h ( n ) h(n) h(n) 大于实际代价,导致找到的路径不是最优解;
- 避坑:优先使用曼哈顿/切比雪夫距离,确保 h ( n ) ≤ h(n) \leq h(n)≤ 实际代价。
-
开放列表重复入队:
- 坑:同一节点多次入队,导致优先队列膨胀,效率降低;
- 避坑:用
unordered_map记录开放列表中的节点,更新时直接修改优先级(或标记后跳过重复节点)。
-
坐标越界:
- 坑:访问网格时 x / y x/y x/y 超出范围,导致程序崩溃;
- 避坑:获取邻居时严格检查坐标范围( n x ≥ 0 & & n x < r o w s & & n y ≥ 0 & & n y < c o l s nx \geq 0 \&\& nx < rows \&\& ny \geq 0 \&\& ny < cols nx≥0&&nx<rows&&ny≥0&&ny<cols)。
-
斜向移动代价错误:
- 坑:斜向移动代价与直走相同(如都为10),导致路径不合理;
- 避坑:斜向代价设为 2 × \sqrt{2} \times 2 × 直走代价(如 14 ≈ 10×1.414)。
-
内存泄漏:
- 坑:未释放
Node内存,导致内存泄漏; - 避坑:使用
free_grid函数统一释放所有节点内存。
- 坑:未释放
六、A* vs 其他路径算法(核心差异)
| 算法 | 核心特点 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| A* | 启发式 f ( n ) = g + h f(n)=g+h f(n)=g+h | 最优解+高效率 | 依赖高质量启发函数 | 静态地图路径规划 |
| Dijkstra | 仅用 g ( n ) g(n) g(n) | 绝对最优解 | 效率低(遍历所有节点) | 无启发信息的场景 |
| BFS | 等代价搜索 | 实现简单 | 仅适用于等代价网格,效率低 | 简单网格寻路 |
| 贪心算法 | 仅用 h ( n ) h(n) h(n) | 搜索速度最快 | 无法保证最优解 | 对路径最优性要求低的场景 |
七、总结
核心要点回顾
- A 核心 *:通过 f ( n ) = g ( n ) + h ( n ) f(n) = g(n) + h(n) f(n)=g(n)+h(n) 评估节点优先级,结合全局最优( g ( n ) g(n) g(n))和启发引导( h ( n ) h(n) h(n));
- 核心组件 :
- 开放列表:优先队列,按 f ( n ) f(n) f(n) 排序;
- 关闭列表:记录已探索节点;
- 启发函数:曼哈顿/切比雪夫距离(保证可采纳性);
- 关键优化 :
- 数据结构:用数组替代哈希集合,避免重复入队;
- 启发函数:保证可采纳性,动态加权提升效率;
- 剪枝:跳过障碍物/越界节点;
- 核心优势:在保证最优解的前提下,搜索效率远高于 Dijkstra/BFS。
学习建议
- 先实现 4 方向网格寻路,理解核心流程;
- 扩展到 8 方向,掌握斜向移动的代价计算;
- 优化开放列表,解决重复入队问题;
- 将 A* 应用到实际场景(如游戏寻路、机器人导航)。
记住:A* 算法的本质是"有引导的全局搜索"------ g ( n ) g(n) g(n) 保证不遗漏最优解, h ( n ) h(n) h(n) 引导搜索方向,两者结合是路径规划的"黄金组合"。好的启发函数和数据结构优化,是 A* 高效运行的关键。