目录
一、简单迷宫(百度笔试题)
1.1 题目
1.2 思路
从入口开始不断的四个方向探索,直到走到出口,走的过程中我们借助栈记录走过路径的坐标,栈记录坐标有两方面的作用,一方面是记录走过的坐标,一方面方便走到死路时进行回溯找其他的通路(后进先出的特性,走到死路直接可以出掉该坐标)。
1.3 具体实现
1.3.1 建立迷宫
由于牛客的编译器是支持c语言的变长数组(c99特性),但是vs编译器只支持c98,不能使用变长数组,所以为了在两个平台都能运行,就需要用动态开辟空间来完成变长数组的功能。
二维数组如何该如何动态开辟呢?
动态二维数组不能直接malloc出来,需要通过指针数组来完成该功能,指针数组的元素都是指针类型的,在通过指针在链接到一个数组,就能完成动态二维数组的功能。
动态开辟二维数组代码:
int N, M;
scanf("%d %d", &N, &M);
int** maze = (int**)malloc(N * sizeof(int*));
for (int i = 0; i < N; i++)
{
maze[i] = (int*)malloc(M * sizeof(int));
}
释放动态二维数组注意事项
有申请就要有释放,不让内存泄漏
同样释放不能直接释放maze,需要先把数组指针指向的空间先给释放,在释放maze
// 释放动态二维数组空间
for (int i = 0; i < M; i++)
{
free(maze[i]);
}
free(maze);
由于需要存坐标(两个变量),所以我们可以创建一个结构体
// 定义坐标结构体
typedef struct Position
{
int row;
int col;
}Pos;
1.3.2 探测路径递归函数
结束条件就是找到入口,每一个位置就往上下左右四个方向探测,其中就会碰到两个问题
- 走过路就不在递归进去了,不然会死循环,如何处理走过位置?
走过的路需要封住,每次递归都会往四个方向探测,如:递归下方坐标,该次递归还会探测上方坐标,如果不标识,他就会在递归回去,造成死循环。
- 碰到四个方向都是死路怎么办?
四个方向都走不通(越界和墙壁),就返回上次递归的位置,探测上次递归还有没有路可以走,所以碰到死路,就会返回,返回之前把死路坐标给出栈。
递归过程:
-
结束条件:找到出口
-
单层递归逻辑:
- 当前位置入栈
- 判断是否在出口位置,在就返回不在递归,不在就继续递归
- 当前位置给封住,不让下次递归走到该位置
- 依次探测上下左右四个方向,其中某一个方向能走的通,就递归到位置,在去探测路径
- 遇到死路把当前位置在出栈,返回到上次递归的位置
-
函数返回值:bool类型,找到出口就不在递归(该题到达出口通路只有一条) 不是void类型就会找到出口后还会探测其他方向
1.3.3 打印路径
现在还有一个问题是,大家都直到栈的特性后进先出,但是题目要求输出的坐标按顺序,可我们栈输出的坐标是反着的,额外在创建一个临时栈来翻转路径,在输出临时栈的路径。
1.4 完整代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
// 定义坐标结构体
typedef struct Position
{
int row;
int col;
}Pos;
// 栈的数据类型改为坐标结构体
typedef Pos STDataType;
typedef struct Stack
{
STDataType* a; // 数组地址
int top; // 数据个数
int capacity; // 容量
}Stack;
void StackInit(Stack* ps);
bool StackEmpty(Stack* ps);
void StackPush(Stack* ps, STDataType x);
void StackPop(Stack* ps);
STDataType StackTop(Stack* ps);
int StackSize(Stack* ps);
void StackDestroy(Stack* ps);
// 初始化
void StackInit(Stack* ps)
{
assert(ps);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
// 栈为空判断
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
// 入栈
void StackPush(Stack* ps, STDataType x)
{
assert(ps);
// 栈容量检查
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, newCapacity * sizeof(STDataType));
assert(tmp);
ps->a = tmp;
ps->capacity = newCapacity;
}
// 插入数据
ps->a[ps->top] = x;
ps->top++;
}
// 出栈
void StackPop(Stack* ps)
{
assert(ps);
// 空栈不能出
assert(!StackEmpty(ps));
ps->top--;
}
// 取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
// 空栈不能取
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
// 栈的大小
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
// 销毁
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
// ===============以下为关键代码==================
// 打印迷宫
void PrintMaze(int** maze, int N, int M)
{
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
printf("%d ", maze[i][j]);
}
printf("\n");
}
}
// 是否能探测函数
bool IsPass(int** maze, int N, int M, Pos pos)
{
if (pos.row >= 0 && pos.row < N &&
pos.col >= 0 && pos.col < M &&
maze[pos.row][pos.col] == 0)
{
return true;
}
else
{
return false;
}
}
// 探测路径函数
bool GetMazePath(int** maze, int N, int M, Pos cur, Stack* path)
{
StackPush(path, cur);
// 找到出口条件
if (cur.row == N - 1 && cur.col == M - 1)
return true;
// 探测的过程,自己走过的地方标识下
maze[cur.row][cur.col] = 2;
Pos next;
// 上
next = cur;
next.row -= 1;
if (IsPass(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next, path))
return true;
}
// 下
next = cur;
next.row += 1;
if (IsPass(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next, path))
return true;
}
// 左
next = cur;
next.col -= 1;
if (IsPass(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next, path))
return true;
}
// 右
next = cur;
next.col += 1;
if (IsPass(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next, path))
return true;
}
// 碰到死路就出掉入进去的坐标
StackPop(path);
// 四个方向都不会通就返回
return false;
}
// 打印路径
void PrintPath(int** maze, int N, int M, Stack* path)
{
// 创建临时栈把数据倒过去
Stack tmp;
StackInit(&tmp);
while (!StackEmpty(path))
{
StackPush(&tmp, StackTop(path));
StackPop(path);
}
// 输出临时栈数据,就能实现题目输出要求
while (!StackEmpty(&tmp))
{
STDataType top = StackTop(&tmp);
printf("(%d,%d)\n", top.row, top.col);
StackPop(&tmp);
}
StackDestroy(&tmp);
}
int main()
{
int N; // 行
int M; // 列
scanf("%d %d", &N, &M);
// 创建迷宫
int** maze = (int**)malloc(N * sizeof(int*));
for (int i = 0; i < N; i++)
{
maze[i] = (int*)malloc(M * sizeof(int));
}
// 初始化迷宫
for (int i = 0; i < N; i++)
for (int j = 0; j < M; j++)
scanf("%d", &maze[i][j]);
// 开始位置坐标为0,0
Pos entry = { 0, 0 };
Stack path;
StackInit(&path);
if (GetMazePath(maze, N, M, entry, &path))
{
// 打印路径
PrintPath(maze, N, M, &path);
}
StackDestroy(&path);
// 释放动态二维数组空间
for (int i = 0; i < N; i++)
{
free(maze[i]);
}
free(maze);
return 0;
}
二、进阶迷宫(滴滴笔试题)
2.1 题目
2.2 思路
相比简单迷宫,多出了体力值,走到出口,体力值为负数,就不能逃出迷宫,并且有多条路径能到达出口。
遇到出口不直接返回结束,而是看之前走过的路是否还有方向优先能到出口。
- 不能再用返回值,因为找到第一条路径就会返回
- 由于需要找到多条路径,找最短路径,所以一些位置会被多次走,所以递归结束时需要把这些位置解开。
- 额外创建一个栈minPath,用来放到达出口的最短路径
- 走到出口,体力值为负数,所以不能逃出。
- 比较当前路径和最短路径的栈,当前路径短,就把数据复制给最短路径,前提是,到达出口时,生命值不能为负数,注意要深拷贝,不能直接赋值(浅拷贝)。
为什么不能直接赋值(浅拷贝)?-
内存泄漏,直接赋值最开始申请最短路径的栈空间就找不到了。
-
直接赋值,导致两个栈的地址都是一样的,每次用来栈在变化的时候,存放最短路径的栈也会跟着变化
-
销毁两次一块空间
-
- 判断最短路径是否为空,不为空就能逃出去,为空就逃出去。
2.3 栈复制函数(深拷贝)
- dest:复制目的地, src:数据源
- 把目的地的的空间改成和数据源一样大
- 通过内存拷贝函数(memcpy)把数据源的数据全部过来,数据个数为capacity,不能是top,不然内存泄漏,free的时候会出问题。
- 把数据源的大小和存了多少数据的信息同样拷贝过来
// 栈复制函数
void StackCopy(Stack* dest, Stack* src)
{
dest->a = (STDataType*)malloc(src->capacity * sizeof(STDataType));
memcpy(dest->a, src->a, src->capacity * sizeof(STDataType));
dest->capacity = src->capacity;
dest->top = src->top;
}
2.4 完整代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
typedef struct Position
{
int row;
int col;
}Pos;
typedef Pos STDataType;
typedef struct Stack
{
STDataType* a; // 数组地址
int top; // 数据个数
int capacity; // 容量
}Stack;
void StackInit(Stack* ps);
bool StackEmpty(Stack* ps);
void StackPush(Stack* ps, STDataType x);
void StackPop(Stack* ps);
STDataType StackTop(Stack* ps);
int StackSize(Stack* ps);
void StackDestroy(Stack* ps);
// 初始化
void StackInit(Stack* ps)
{
assert(ps);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
// 栈为空判断
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
// 入栈
void StackPush(Stack* ps, STDataType x)
{
assert(ps);
// 栈容量检查
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, newCapacity * sizeof(STDataType));
assert(tmp);
ps->a = tmp;
ps->capacity = newCapacity;
}
// 插入数据
ps->a[ps->top] = x;
ps->top++;
}
// 出栈
void StackPop(Stack* ps)
{
assert(ps);
// 空栈不能出
assert(!StackEmpty(ps));
ps->top--;
}
// 取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
// 空栈不能取
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
// 栈的大小
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
// 销毁
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
// =============== 以下为迷宫关键代码 ==================
// 打印迷宫
void PrintMaze(int** maze, int N, int M)
{
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
printf("%d ", maze[i][j]);
}
printf("\n");
}
}
// 是否能探测函数
bool IsPass(int** maze, int N, int M, Pos pos)
{
if (pos.row >= 0 && pos.row < N &&
pos.col >= 0 && pos.col < M &&
maze[pos.row][pos.col] == 1)
{
return true;
}
else
{
return false;
}
}
// 栈复制函数
void StackCopy(Stack* dest, Stack* src)
{
dest->a = (STDataType*)malloc(src->capacity * sizeof(STDataType));
memcpy(dest->a, src->a, src->capacity * sizeof(STDataType));
dest->capacity = src->capacity;
dest->top = src->top;
}
// 探测递归函数
void GetMazePath(int** maze, int N, int M, Pos cur, Stack* path, Stack* minPath, int HP)
{
StackPush(path, cur);
// 找到出口条件
if (cur.row == 0 && cur.col == M - 1)
{
// minPath栈为空,minPath直接设为第一次到达路径
if (HP >= 0 && StackEmpty(minPath) ||
StackSize(path) < StackSize(minPath))
{
StackDestroy(minPath);
StackCopy(minPath, path);
}
}
// 探测的过程,自己走过的地方标识下
maze[cur.row][cur.col] = 2;
Pos next;
// 上
next = cur;
next.row -= 1;
if (IsPass(maze, N, M, next))
{
GetMazePath(maze, N, M, next, path, minPath, HP - 3);
}
// 下
next = cur;
next.row += 1;
if (IsPass(maze, N, M, next))
{
GetMazePath(maze, N, M, next, path, minPath, HP);
}
// 左
next = cur;
next.col -= 1;
if (IsPass(maze, N, M, next))
{
GetMazePath(maze, N, M, next, path, minPath, HP - 1);
}
// 右
next = cur;
next.col += 1;
if (IsPass(maze, N, M, next))
{
GetMazePath(maze, N, M, next, path, minPath, HP - 1);
}
// 把当前位置解开,为了让下一条路径可以走
maze[cur.row][cur.col] = 1;
StackPop(path);
}
// 打印入口探测到出口的路线
void PrintPath(int** maze, int N, int M, Stack* path)
{
Stack tmp;
StackInit(&tmp);
while (!StackEmpty(path))
{
StackPush(&tmp, StackTop(path));
StackPop(path);
}
while (!StackEmpty(&tmp))
{
STDataType top = StackTop(&tmp);
printf("[%d,%d]", top.row, top.col);
if (StackSize(&tmp) > 1)
printf(",");
StackPop(&tmp);
}
StackDestroy(&tmp);
}
int main()
{
int N, M, HP;
scanf("%d %d %d", &N, &M, &HP);
int** maze = (int**)malloc(N * sizeof(int*));
for (int i = 0; i < N; i++)
{
maze[i] = (int*)malloc(M * sizeof(int));
}
for (int i = 0; i < N; i++)
for (int j = 0; j < M; j++)
scanf("%d", &maze[i][j]);
// 开始位置坐标为0,0
Pos entry = { 0, 0 };
// 额外一个栈来存最短路径
Stack path, minPath;
StackInit(&path);
StackInit(&minPath);
GetMazePath(maze, N, M, entry, &path, &minPath, HP);
if (!StackEmpty(&minPath))
{
PrintPath(maze, N, M, &minPath);
}
else
{
printf("Can not escape!");
}
StackDestroy(&path);
StackDestroy(&minPath);
return 0;
}
总结
简单迷宫和进阶迷宫,核心重点还是在于递归方面,递归的探测和遇到死路回溯,而进阶迷宫特点就是最短路径,并且需要多加理解深拷贝。