要求
封面、目录、每个方法(算法原理说明、代码、运行结果截图)以及收获体会。
打印运行时间
算法原理说明
栈
栈(stack) 是一种特殊的线性表,是一种只允许在表的一端进行插入或删除操作的线性 表。表中允许进行插入和删除操作的一端称为栈顶,最下面的那一端称为栈底。栈顶是动 态的,它由一个称为栈顶指针的位置指示器指示。当栈中没有数据元素时,称为空栈。
栈的主要特点是 “后进先出” ,即后进栈的元素先处理。因此栈 又称为后进先出(LastInFirstOut,LIFO)表。
迷宫探路问题中的若干可通位置形成的通路构成了栈,对栈的运算主要有以下几种。
- 初始化栈
initStack(&s) - 入栈
push(&s,Box) - 出栈
pop(&s) - 读栈顶元素
peek(&s) - 判断栈满
isFull - 判断栈空
isEmpty
DFS
深度优先搜索(DFS) 是一种用于遍历或搜索树或图的算法。在迷宫生成的上下文中,深度优先搜索可以用来探索从起点到终点的路径。其思想是从起点开始,向一个方向尽可能深入地走,直到无法继续为止(即到达死胡同),然后回溯到上一个岔路口,继续探索其他路径,直到所有的路径都已遍历。
深度优先搜索迷宫生成的特点是相对简单易实现,并且生成的迷宫通常具有长而窄的路径特征,适合于需要用户深思熟虑的迷宫游戏。
代码
迷宫的打印
先不谈具体算法,首先我们需要实现迷宫的物理存储,可以采用二维数组来存储一个二维平面地图,使用0表示是通路,1表示是墙 这里我们先自己创建一个,之后再用递归等算法自动创建迷宫。
int maze[ROW+2][COL+2]={
{1,1,1,1,1,1,1,1},
{1,0, 1, 0, 0, 0, 0,1},
{1,0, 1, 0, 1, 1, 0,1},
{1,0, 0, 0, 1, 0, 0,1},
{1,1, 1, 0, 1, 0, 1,1},
{1,0, 0, 0, 0, 0, 0,1},
{1,0, 1, 1, 1, 1, 0,1},
{1,1,1,1,1,1,1,1}
};
这里四周多出一圈1,是为了在每一个方向上都有四个方向可以选。
定义栈的结构
我们用一个结构体来封装栈:
#define MAX_STACK 100
typedef struct {
int x; // 行坐标
int y; // 列坐标
int dir; // 方向(0~3 或 -1 表示未尝试)
} Box; // 栈中存放的元素
typedef struct {
Box data[MAX_STACK];
int top; // 栈顶索引,-1 表示空栈
} Stack;
栈的基本操作
//初始化栈
void initStack(Stack *s)
{
s->top=-1;
}
//判空
bool isEmpty(Stack *s)
{
return s->top==-1;
}
//判满
bool isFull(Stack *s)
{
return s->top==MAX_STACK-1;
}
//压入
void push(Stack *s,Box b)
{
if(!isFull(s))
{
s->top++;
s->data[s->top]=b;
}
}
//弹出并返回
Box pop(Stack *s)
{
Box empty={-1,-1,-1};
if(isEmpty(s))return empty;
Box b=s->data[s->top];
s->top--;
return b;
}
方向数组
用结构体存储方向向量,配合循环尝试四个方向:
typedef struct {
int incX; // 行增量
int incY; // 列增量
} Direction;
Direction direct[4] = {
{-1, 0}, // 上
{ 1, 0}, // 下
{ 0, -1}, // 左
{ 0, 1} // 右
};
DFS寻路逻辑
- 将起点入栈,并标记为已访问(
maze[x][y] = -1)。 - 循环直到栈空:
- 弹出栈顶元素作为当前考察点
(x, y)。 - 从
di记录的下一个方向开始尝试(di = cur.di + 1)。 - 依次尝试四个方向:
- 计算邻居坐标
(nx, ny)。 - 若邻居可通(
maze[nx][ny] == 0):- 将当前点(更新方向索引为
di)压回栈,以便以后回溯。 - 移动到邻居点,标记已访问。
- 将邻居点(方向索引
-1)压入栈。 - 若邻居是终点,则成功返回。
- 否则跳出方向循环,下一次循环直接处理栈顶(邻居点)。
- 将当前点(更新方向索引为
- 若不可通,则
di++继续尝试下一个方向。
- 计算邻居坐标
- 如果四个方向都试完仍无通路,则当前点被丢弃(未重新压回),自然实现回溯
- 弹出栈顶元素作为当前考察点
代码:
//寻路函数
bool findPath(int maze[ROW+2][COL+2],Direction direct[],Stack *s)
{
Box temp;
int x,y,di;
int line,col;//下一步的坐标
maze[1][1]=-1;
temp=(Box){1,1,-1};
push(s,temp);
while(!isEmpty(s))
{
temp=pop(s);
x=temp.x;
y=temp.y;
di=temp.di+1;
while(di<4)
{
line=x+direct[di].incX;
col=y+direct[di].incY;
if(maze[line][col]==0)
{
temp=(Box){x,y,di};
push(s,temp);
x=line;
y=col;
maze[x][y]=-1;
if(x==ROW&&y==COL){
push(s,(Box){x,y,-1});
return true;
}else {
di=0;
}
}else{
di++;
}
}
}
return false;
}
完整代码示例
//迷宫问题DFS
#include<stdio.h>
#include<stdbool.h>
#include<time.h>
#define ROW 6
#define COL 6
#define MAX_STACK 100
//方向结构体,用于存储每个方向移动的值
typedef struct{
int incX;
int incY;
}Direction;
//当前位置
typedef struct{
int x;
int y;
int di;
}Box;
typedef struct
{
Box data[MAX_STACK];
int top; //固定数组+移动的top=动态的栈顶指针
}Stack;
//栈的基本操作
//初始化栈
void initStack(Stack *s)
{
s->top=-1;
}
//判空
bool isEmpty(Stack *s)
{
return s->top==-1;
}
//判满
bool isFull(Stack *s)
{
return s->top==MAX_STACK-1;
}
//压入
void push(Stack *s,Box b)
{
if(!isFull(s))
{
s->top++;
s->data[s->top]=b;
}
}
//弹出并返回
Box pop(Stack *s)
{
Box empty={-1,-1,-1};
if(isEmpty(s))return empty;
Box b=s->data[s->top];
s->top--;
return b;
}
//寻路函数
bool findPath(int maze[ROW+2][COL+2],Direction direct[],Stack *s)
{
Box temp;
int x,y,di;
int line,col;//下一步的坐标
maze[1][1]=-1;
temp=(Box){1,1,-1};
push(s,temp);
while(!isEmpty(s))
{
temp=pop(s);
x=temp.x;
y=temp.y;
di=temp.di+1;
while(di<4)
{
line=x+direct[di].incX;
col=y+direct[di].incY;
if(maze[line][col]==0)
{
temp=(Box){x,y,di};
push(s,temp);
x=line;
y=col;
maze[x][y]=-1;
if(x==ROW&&y==COL){
push(s,(Box){x,y,-1});
return true;
}else {
di=0;
}
}else{
di++;
}
}
}
return false;
}
//打印输出路径
void printPath(Stack *s){
if(isEmpty(s)){
printf("The path is empty.\n");
return;
}
printf("Path found (from start to end):\n");
for(int i=0;i<=s->top;i++)
{
printf("(%d,%d)",s->data[i].x,s->data[i].y);
if(i<s->top)printf("->");
}
printf("\n");
}
int main()
{
clock_t start, end;
double cpu_time_used;
start = clock(); // 记录开始时刻
int maze[ROW+2][COL+2]={
{1,1,1,1,1,1,1,1},
{1,0, 1, 0, 0, 0, 0,1},
{1,0, 1, 0, 1, 1, 0,1},
{1,0, 0, 0, 1, 0, 0,1},
{1,1, 1, 0, 1, 0, 1,1},
{1,0, 0, 0, 0, 0, 0,1},
{1,0, 1, 1, 1, 1, 0,1},
{1,1,1,1,1,1,1,1}
};
Direction direct[4]={
{-1,0}, //上
{1,0}, //下
{0,-1}, //左
{0,1}, //右
};
Stack stack;
initStack(&stack);
if(findPath(maze,direct,&stack))
{
printPath(&stack);
}else{
printf("Path not found!\n");
}
end = clock(); // 记录结束时刻
cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
printf("The running time is: %f s\n", cpu_time_used);
return 0;
}
运行结果与复杂度
时间复杂度 :每个格子最多被访问一次,O(M*N)。
空间复杂度 :栈对多存储一条路径上的格子,O(M+N)。
对于6*6迷宫,程序 瞬间 输出路径,例如:
Path found (from start to end):
(1,1)->(2,1)->(3,1)->(3,2)->(3,3)->(2,3)->(1,3)->(1,4)->(1,5)->(1,6)->(2,6)->(3,6)->(3,5)->(4,5)->(5,5)->(5,6)->(6,6)
The running time is: 0.000000 s
总结扩展
通过数组手写栈并实现非递归 DFS,我们不仅加深了对栈“后进先出”的理解,也掌握了一种经典的回溯算法实现方式。
栈的运算虽然简单(push、pop、peek、isEmpty),但组合起来就能解决迷宫寻路、括号匹配、表达式求值等许多实际问题。
之后可以 用链表实现栈 ,动态分配节点,没有固定容量限制, 求最短路径 ,将栈改为队列,DFS改为BFS。
如果还有时间的话,会将迷宫可视化做出来。