本篇 核心知识点:BFS 广度优先搜索完整流程、DFS 深度优先搜索递归实现、A * 启发式寻路算法
一、BFS 广度优先搜索(迷宫最优路径)
1. 概念
广度优先搜索以队列 为底层容器,从起点分层向外扩散遍历网格,每一层代表移动一步,天然保证寻路结果为最短路径,属于暴力无启发式搜索。
2. 核心特性
-
存储结构:
queue队列存储坐标点; -
遍历规则:起点入队,循环取出队头,遍历上下左右四向邻点;
-
判重机制:二维数组记录每个格子的前驱坐标,回溯生成完整路径;
-
终止条件:取出坐标等于出口坐标时退出循环;
-
适用:网格最短路径、怪物巡逻范围计算。
3. 完整代码示例
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
// 坐标结构体
struct Point{
int x, y;
Point(int x_ = 0, int y_ = 0) : x(x_), y(y_) {}
};
// 四向偏移:上、下、左、右
int dir[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}};
// 迷宫地图:0墙 1通路 3起点 4终点
int maze[10][10] = {
{0,0,0,0,0,0,0,0,0,0},
{0,1,1,0,1,1,1,0,1,0},
{0,3,1,0,1,0,1,0,1,0},
{0,1,1,1,1,0,1,1,1,0},
{0,0,0,0,1,0,0,0,1,0},
{0,1,1,1,1,1,1,0,1,0},
{0,1,0,0,0,0,1,0,1,0},
{0,1,1,1,1,1,1,1,1,0},
{0,1,0,0,0,0,0,0,4,0},
{0,0,0,0,0,0,0,0,0,0}
};
// 前驱数组,记录每个格子的父坐标
Point pre[10][10];
void BFS(){
queue<Point> q;
// 初始化前驱全部为(-1,-1)
for(int i=0;i<10;i++)
for(int j=0;j<10;j++)
pre[i] = Point(-1,-1);
// 寻找起点
int sx,sy,ex,ey;
for(int i=0;i<10;i++)
for(int j=0;j<10;j++){
if(maze[i][j]==3) sx=i,sy=j;
if(maze[i][j]==4) ex=i,ey=j;
}
q.push(Point(sx,sy));
while(!q.empty()){
Point cur = q.front();
q.pop();
// 到达出口,结束搜索
if(cur.x == ex && cur.y == ey) break;
// 遍历四个方向
for(int d=0;d<4;d++){
int nx = cur.x + dir[d][0];
int ny = cur.y + dir[d][1];
// 边界+通路判断,且未访问过
if(nx>=0&&nx<10&&ny>=0&&ny && maze[nx][ny]==1 && pre[nx].x==-1)
{
pre[nx] = cur; // 记录前驱
q.push(Point(nx, ny));
}
}
}
// 回溯输出路径(从终点倒推起点)
vector<Point> path;
Point p(ex, ey);
while(p.x != -1){
path.push_back(p);
p = pre[p.x][p.y];
}
// 逆序打印
for(auto it = path.rbegin(); it != path.rend(); it++)
cout << "(" << it->x << "," << it->y << ") ";
}
int main(){
BFS();
return 0;
}
4. 拓展:全地图拾取金币改造
修改循环终止条件,直到队列为空,不再检测出口;走过格子标记为障碍,遍历所有连通通路完成金币拾取。
二、DFS 深度优先搜索(递归实现)
1. 概念
深度优先依靠函数递归实现,一条路走到底,无路可走再回溯分支,底层等价于栈结构。
2. 特性
-
无需队列 / 栈容器,利用程序调用栈;
-
无法保证最短路径,代码简洁;
-
递归深度过大会栈溢出,大地图推荐显式栈;
-
适合全地图遍历、迷宫遍历类需求。
3. 代码示例(递归 DFS)
int dir[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};
int maze[10][10];
void DFS(int x, int y){
// 越界/墙直接返回
if(x<0||x>=10||y<0||y>=10||maze[x][y]==0) return;
maze[x][y] = 6; // 标记已走
// 到达出口
if(maze[x][y]==4) return;
// 四向递归
for(int d=0;d<4;d++){
int nx = x + dir[d][0];
int ny = y + dir[d][1];
DFS(nx, ny);
}
}
拓展
手写链式栈实现非递归 DFS,规避递归栈溢出问题。
三、A * 启发式寻路算法(本节课核心)
3.1 基础概念
A * 是启发式最优路径搜索算法,不属于暴力遍历,通过估价函数筛选最优前进方向,大幅减少无效遍历,游戏、导航工业标准寻路方案。
核心公式:F = G + H
-
G:实际代价,起点到当前格子真实移动消耗; -
H:预估代价,当前格子到终点的估算距离(启发值); -
F:总综合代价,每次从 Open 列表选取F 最小节点前进。
3.2 两大估价 H 计算公式
(1)曼哈顿距离(2D 平面四向移动专用)
公式:H = abs(x_end - x_cur) + abs(y_end - y_cur)
特性:仅计算横竖步数,不支持斜向,适合仅上下左右行走的 2D 游戏。
(2)欧几里得距离(2D/3D 八向通用)
公式:H = sqrt((xe-xc)² + (ye-yc)²)
特性:支持斜向移动,3D 地形、战棋游戏优先选用。
3.3 Open 列表 & Close 列表
概念
-
Open 列表:待遍历候选节点集合,存放已发现但未处理的格子;
-
Close 列表:已完全处理、不再二次遍历的格子集合;
操作规则
-
起点先存入 Open 列表;
-
每次取出 Open 中 F 最小节点,移入 Close;
-
遍历该节点四 / 八邻格,分三类分支处理;
三类邻格处理逻辑
-
邻格在 Close 列表:跳过,防止循环折返;
-
邻格在 Open 列表:对比新 G 值,新路径更短则更新 F/G 与前驱;
-
邻格不在任何列表:计算 F/G/H,存入 Open 并记录前驱。
3.4 A * 完整执行步骤
-
定义地图、坐标结构体(存储 x,y,F,G,H, 前驱指针);
-
起点入 Open 列表;
-
循环:Open 为空则寻路失败;
-
取出 Open 最小 F 节点,移入 Close;
-
判断该节点是否终点,是则回溯生成路径;
-
遍历四周邻格,按三类规则更新 Open;
-
重复循环直至找到终点。
3.5 代码基础框架
#include <vector>
#include <list>
#include <cmath>
using namespace std;
struct Point{
int x,y;
int F,G,H;
Point* parent;
Point(int x_=0,int y_):x(x_),y(y_),F=0,G=0,H=0,parent(nullptr){}
};
// 曼哈顿估价
int CalcH(int cx,int cy,int ex,int ey){
return abs(ex-cx) + abs(ey-cy);
}
// 寻找Open列表F最小节点
Point* GetMinPoint(list<Point*>& open){
Point* minP = open.front();
for(auto p : open)
if(p->F < minP->F) minP = p;
return minP;
}
bool AStar(int sx,int sy,int ex,int ey,int map[10][10]){
list<Point*> open, close;
Point* start = new Point(sx,sy);
start->H = CalcH(sx,sy,ex,ey);
start->F = start->G + start->H;
open.push_back(start);
while(!open.empty()){
Point* cur = GetMinPoint(open);
open.remove(cur);
close.push_back(cur);
// 到达终点
if(cur->x == ex && cur->y == ey) return true;
// 四向遍历
int dir[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};
for(int d=0;d<4;d++){
int nx = cur->x + dir[d][0];
int ny = cur->y + dir[d];
// 边界、墙壁过滤
if(nx<0||nx>=10||ny<0||ny>=10||map[nx][ny]==0) continue;
Point* next = new Point(nx,ny);
next->G = cur->G + 10; // 横向移动代价10
next->H = CalcH(nx,ny,ex,ey);
next->F = next->G + next->H;
next->parent = cur;
// 判断是否在Close
bool inClose = false;
for(auto p : close)
if(p->x == nx && p->y == ny) {inClose=true;break;}
if(inClose) continue;
// 判断是否在Open
bool inOpen = false;
for(auto p : open){
if(p->x == nx && p->y == ny){
inOpen = true;
// 新路径更优,更新
if(next->G < p->G){
p->G = next->G;
p->F = p->G + p->H;
p->parent = cur;
}
break;
}
}
if(!inOpen) open.push_back(next);
}
}
return false; // 无通路
}
3.6 A * 优化方案(高频拓展)
-
Open 列表优化:普通 list 遍历找最小值效率低,改用 ** 优先队列(堆)** 自动维护最小 F 节点;
-
估价函数适配:2D 四向用曼哈顿,八向 / 3D 用欧几里得;
-
内存优化:复用坐标对象,频繁 new 易产生内存碎片;
-
地图分层:超大地图分块 A*,降低单次遍历范围。
3.7 A * 对比 DFS/BFS
-
DFS:无估价,遍历无序,无最短保证,适合全遍历;
-
BFS:全局暴力分层,一定最短,超大地图效率极低;
-
A*:启发式定向搜索,兼顾效率与最短路径,工业首选。