问题描述
求迷宫图指定入口格子到出口格子的路径。最优解是最短的那条路径。
定义迷宫
迷宫大小M*N和最大值:
#define M 8
#define N 8
#define MaxSize 500
M*N加上外面一圈墙。0可走,1是障碍物。
int Maze[M+2][N+2]= {
{1,1,1,1,1,1,1,1,1,1},
{1,0,0,1,0,0,0,1,0,1},
{1,0,0,1,0,0,0,1,0,1},
{1,0,0,0,0,1,1,0,0,1},
{1,0,1,1,1,0,0,0,0,1},
{1,0,0,0,1,0,0,0,0,1},
{1,0,1,0,0,0,1,0,0,1},
{1,0,1,1,1,0,1,1,0,1},
{1,1,0,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1,1} };
图的邻接表存储结构
邻接表中每个元素都是一个头结点(包括数据域data
第一个边结点指针*firstarc
),每个元素都可能是别人的边结点(包括结点编号adjvex
下一结点指针域*nextarc
以及权重weight
)。
#迷宫图对应的邻接表:
定义邻接表:
//边结点类型//
typedef struct ANode {
int i, j;
struct ANode *nextarc;
}ArcNode;
//头结点类型//
typedef struct {
ArcNode *firstarc;
}VNode;
//邻接表类型//
typedef struct {
VNode adjlist[M+ 2][N+ 2];
}AdjGraph;
创建图的邻接表:
//建立迷宫数组对应的邻接表//
void CreateAdj(AdjGraph *&G, int A[M+2][N+2])
{
ArcNode *p;//用来创建结点的工具指针
G = (AdjGraph*)malloc(sizeof(AdjGraph));
int i, j;//定义迭代器
//给邻接表中所有头节点的指针域置初值//
for (i = 0; i < M + 2; i++)
for (j = 0; j < N + 2; j++)
G->adjlist[i][j].firstarc = NULL;
//检查迷宫中每个元素//
for (i = 1; i <= M; i++)
for (j = 1; j <= N; j++)
if (Maze[i][j] == 0) {
int di = 0;
int i1, j1;
while (di < 4)
{
switch (di)
{
case 0:
i1 = i - 1;
j1 = j;
break;
case 1:
i1 = i;
j1 = j + 1;
break;
case 2:
i1 = i + 1;
j1 = j;
break;
case 3:
i1 = i, j1 = j - 1;
break;
}
if (Maze[i1][j1] == 0) {
p = (ArcNode*)malloc(sizeof(ArcNode));
p->i = i1;
p->j = j1;
//放边结点的链表是头插法建立的//
p->nextarc = G->adjlist[i][j].firstarc;
G->adjlist[i][j].firstarc = p;
}
di++;
}
}
}
图的遍历
#深度优先DFS(递归):类似二叉树先序遍历
定义迷宫格子和路径:
typedef struct
{ int i; //当前格子的行号
int j; //当前格子的列号
int di;
} Box;
typedef struct
{ Box data[MaxSize];
int length;
} PathType; //path结构配合递归达到伪栈的目的
定义访问标记数组和路径计数器:
int visited[M+2][N+2] = { 0 };
int count = 0;
DFS打印所有路径:
-
我不在函数里面创建path而是作为参数传进去是因为递归。这里 path+递归 其实起到了栈的作用。
-
每次调用先判断一次入口是否等于出口。再按条件递归或者结束过程返回上一层调用。
-
用一个ArcNode标记当前的递归入口。
void DFSprintAllPath(AdjGraph *G, int xi,int yi,int xe,int ye,PathType path) {
//进path//
path.data[path.length].i = xi;
path.data[path.length].j = yi;
path.length++;
visited[xi][yi] = -1;
//找到出口打印路径(找到的出口在path的尾)//
if (xi == xe && yi == ye)
{
printf(" 第%d条路径: ", ++count);
for (int k = 0; k < path.length; k++)
printf("(%d,%d) ", path.data[k].i, path.data[k].j);
printf("\n");
}
//DFS部分//
ArcNode *p;
p = G->adjlist[xi][yi].firstarc;//p指向顶点的第一个邻接点
while (p != NULL) {
if (visited[p->i][p->j] == 0)
DFSfindAllPath(G, p->i, p->j, xe, ye,path);
//返回到上一层后指向下一ArcNode//
p = p->nextarc;
}
//走到这里说明此路不通//
//最后调用dfs的格子设置成没走过,返回上一层调用,相当于退栈//
visited[xi][yi] = 0;
}
测试一下:
int main() {
AdjGraph *G;
CreateAdj(G, Maze);
printf("所有的迷宫路径:\n");
PathType path;
path.length = 0;
DFSprintAllPath(G, 1, 1, 8, 8,path);
return 0;
}
用栈实现非递归的DFS求解迷宫问题:
栈
顺序栈结构的定义包含一个data
数组和top
栈顶指针(int类型,存放栈顶元素在data数组中的下标)。
进栈push(top++),退栈pop(top–)。
格子和栈的定义
//格子和栈//
typedef struct {
int i;
int j;
int di; //下一相邻可走方位的方位号
}Box;
typedef struct {
Box data[MaxSize];
int top; //栈顶指针
}StType; //顺序栈类型
具体求解过程:
//求解路径为:(xi,yi)->(xe,ye)//
void MazePath(int xi, int yi, int xe, int ye) {
//定义指示当前格子的变量//
int i, j, di;
//声明栈st并初始化栈顶指针//
StType st;
st.top = -1;
//声明放最短路径的栈//
StType minpath;
int minlen=MaxSize;
//用来数找到几条路径的计数器//
int count=0;
//入口格子进栈//
st.top++;
st.data[st.top].i = xi;
st.data[st.top].j = yi;
st.data[st.top].di = -1;//刚进栈的格子方向置为-1表示尚未试探周围
Maze[xi][yi] = -1;
//栈不为空时循环//
while (st.top > -1)
{
//取栈顶格子(最后进栈的那个格子)//
i = st.data[st.top].i;
j = st.data[st.top].j;
di = st.data[st.top].di;
//找到出口,输出路径//
if (i == xe && j == ye)
{
//输出这条路径//
printf("第%d条:\n",++count);
for (int k = 0; k <= st.top; k++)
{
printf("(%d,%d)", st.data[k].i, st.data[k].j);
if ((k + 1) % 5 == 0)
printf("\n");
}
printf("\n");
//更新最短路径栈//
if (st.top + 1 < minlen) {
minpath.top = -1;//初始化最短路径栈顶指针
for (int k = 0; k <= st.top; k++) {
minpath.top++;
minpath.data[k] = st.data[k];
}
minlen = st.top + 1;
}
//回溯//
//出口退栈//
Maze[st.data[st.top].i][st.data[st.top].j] = 0;
st.top--;
//当前格子指向出口前一个格子//
i = st.data[st.top].i;
j = st.data[st.top].j;
di = st.data[st.top].di;
}
//查找(i,j,di)格子的下一个可走格子//
bool find = false;//下一个格子可不可以走的flag
while (di < 4 && !find)
{
//先把当前格子换到di方向上那个格子上//
di++;
switch (di)
{
case 0:
i = st.data[st.top].i - 1;
j = st.data[st.top].j;
break;
case 1:
i = st.data[st.top].i;
j = st.data[st.top].j + 1;
break;
case 2:
i = st.data[st.top].i + 1;
j = st.data[st.top].j;
break;
case 3:
i = st.data[st.top].i;
j = st.data[st.top].j - 1;
break;
}
//再检查这个格子可不可以走(排除走过的和有障碍物的)//
if (Maze[i][j] == 0)
find = true;
}
if (find)//下一个格子可以走
{
st.data[st.top].di = di;//修改原栈顶元素的di值
st.top++;//下一个可走格子进栈
st.data[st.top].i = i;
st.data[st.top].j = j;
st.data[st.top].di = -1;//刚进栈的格子方向置为-1表示尚未试探周围
Maze[i][j] = -1;//避免重复走到格子
}
else {
//当前格子没有可走的下一个格子则设置成其他格子可走并退栈//
Maze[st.data[st.top].i][st.data[st.top].j] = 0;
st.top--;
}
}
//输出最短路径//
printf("最短路径:长度%d\n",minlen);
for (int k = 0; k <= minpath.top; k++)
{
printf("\t(%d,%d)", minpath.data[k].i, minpath.data[k].j);
if ((k + 1) % 5 == 0)
printf("\n");
}
printf("\n");
}
#广度优先BFS(非递归 队列):类似二叉树层次遍历
队列
顺序队,有队头指针和队尾指针,进队rear+1,出队front+1。可能发生假溢出(当rear=MaxSize-1的时候前面是空的)。
用环形队列解决假溢出的问题:
队空条件:rear==front;
队满条件:(rear+1) % MaxSize == front;
任何时候队中有MaxSize-1个元素
进队出队都变成循环+1:
进:rear=(rear+1)%MaxSize;
出:front=(front+1)%MaxSize;
如果用队列找最优解
就用顺序队,不需要循环队列
因为如果用环形队列,出队以后原先格子的位置可能被后面进队的格子覆盖。
当然只要数组足够大就不会出现问题。
下面开始BFS求解迷宫问题
定义队列用的格子:
typedef struct {
int i;
int j;
int pre;//标记前一个格子的位置
}QBox;//用于队列的格子
BFS打印最短路径:
void BFSprintShortPath(AdjGraph *G, int xi, int yi, int xe, int ye) {
QBox qu[500];
int front = -1, rear = -1;
//入口进队//
rear++;
qu[rear].i = xi;
qu[rear].j = yi;
qu[rear].pre = front;
visited[qu[rear].i][qu[rear].j] = -1;
//队不空时循环//
while (front != rear) {
//出队一个//
front++;
//队头在出口时打印路径//
if (qu[front].i == xe && qu[front].j == ye) {
printf("最短路径(逆向)为:\n");
int i = front;
while (qu[i].pre != -1) {
printf("(%d,%d)", qu[i].i,qu[i].j);
i = qu[i].pre;
}
printf("(%d,%d)", qu[i].i, qu[i].j);
return;
}
ArcNode *p = G->adjlist[qu[front].i][qu[front].j].firstarc;
while (p != NULL) {
if (visited[p->i][p->j] == 0) {
visited[p->i][p->j] = -1;
rear++;
qu[rear].i = p->i;
qu[rear].j = p->j ;
qu[rear].pre = front;
}
p = p->nextarc;
}
}
}
测试一下:
int main() {
AdjGraph *G;
CreateAdj(G, Maze);
BFSprintShortPath(G,1,1,8,8);
return 0;
}
小结
深度优先算法适合找所有路径,邻接表表示总时间为O(n+e)
广度优先算法适合找最短路径,邻接表表示总时间为O(n+e)