迷宫问题是回溯思想的一种运用,一般可以用栈来实现
迷宫问题求解
原题链接迷宫问题.
题目分析
- 题目要求我们先输入两个数代表迷宫的行和列,然后再输入具体的数组内容。这里的起点是左上角,终点是右下角,注意这里有可能让我们输入多组迷宫,所以此时需要用while循环来实现多次输入。另外这个题目中,1代表墙,不能走,0代表路,可以走。
- 从起点开始走迷宫,我们得先告诉程序向哪走,这里按上下左右的顺序依次走,如果走不通就向另一个方向走,要实现这个很简单,只需修改横纵坐标即可。然后判断要走的位置是否是墙(1),如果为0可以向这个地方走一步,四个方向就是四次判断。
- 走完第一步,我们只需要不断重复上一步的步骤就可以走第二步,后面都是重复这个过程。所以这里要用递归实现
- 除了判断四周是否是障碍物之外,还需要判断这个地方之前是否走过了,因为四个方向是都有可能判断一次的,如果不把已经走过的地方标记出来就会走重复了.这里把已经走过的点标记为2。
- 有时候可能会走到死路,也就是上下左右都走不通,这个时候我们就要回到上一步走另一个方向。直到走到终点为止。
- 如何将走过的路径打印出来呢?这里就要用到栈实现,栈里面保存每一步的坐标。栈有先进先出的特点,我们走不通的时候回溯到上一步,栈顶的坐标只需要出栈即可。
- 但是栈里的顺序是倒着打印出来的,所以在打印栈里面的数据的时候只需要再定义一个栈,利用栈实现队列的思想即可打印正着的数据。
代码实现
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
//定义坐标的结构体
typedef struct Postion
{
int row;
int col;
}PT;
//
typedef PT STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
//初始化
void StackInit(ST* ps)
{
assert(ps);
ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL)
{
exit(-1);
}
ps->capacity = 4;
//top初始为0,top指向栈顶元素的下一个元素,top初识为-1,top指向栈顶的元素
ps->top = 0;
}
//销毁
void StackDestory(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
//入栈
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
if (tmp == NULL)
{
exit(-1);
}
else
{
ps->a = tmp;
ps->capacity *= 2;
}
}
ps->a[ps->top] = x;
ps->top++;
}
//出栈
void StackPop(ST* ps)
{
assert(ps);
ps->top--;
}
//取栈顶的数据
STDataType StackTop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->a[ps->top - 1];
}
//求数据的个数
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
//判断是否为空
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
//上面都是栈的代码
ST path;//定义一个全局的栈,来保存走过的路径
//输出栈里面的坐标路径,再用一个栈实现数据的顺序输出
void PrintPath(ST* ps)
{
//将path的数据导入rPath
ST rPath;
StackInit(&rPath);
while (!StackEmpty(&path))//将path里的数据全部放到rPath
{
StackPush(&rPath, StackTop(&path));
StackPop(&path);
}
while (!StackEmpty(&rPath))//输出rPath数据
{
PT top = StackTop(&rPath);
printf("(%d,%d)\n", top.row, top.col);
StackPop(&rPath);
}
StackDestory(&rPath);
}
//判断是否越界或者走不通
bool IsPass(int** maze, int N, int M, PT 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, PT cur)//实现走迷宫的函数
{
StackPush(&path, cur);//将坐标放入栈
//如果走到出口就返回真
if (cur.row == N - 1 && cur.col == M - 1)
{
return true;
}
//探测cur上下左右四个方向
PT next;
//把行和列对应的位置置成2
maze[cur.row][cur.col] = 2;
//上,纵坐标-1
next = cur;
next.row -= 1;
if (IsPass(maze, N, M, next))//判断下一步能否走通
{
if (GetMazePath(maze, N, M, next))//继续走下一步
{
return true;
}
}
//下,纵坐标+1
next = cur;
next.row += 1;
if (IsPass(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next))
{
return true;
}
}
//左,横坐标-1
next = cur;
next.col -= 1;
if (IsPass(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next))
{
return true;
}
}
//右,横坐标+1
next = cur;
next.col += 1;
if (IsPass(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next))
{
return true;
}
}
StackPop(&path);//如果走不通就将栈顶的数据出栈
return false;//回到上一步
}
int main()
{
int N = 0, M = 0;
while (scanf("%d%d", &N, &M) != EOF)//输入行和列
{
int** maze = (int**)malloc(sizeof(int*) * N);//动态创建二维数组
for (int i = 0; i < N; i++)
{
maze[i] = (int*)malloc(sizeof(int) * M);
}
//二维数组的输入
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
scanf("%d", &maze[i][j]);
}
}
StackInit(&path);//初始化栈
PT entry = { 0,0 };//创建起点
if (GetMazePath(maze, N, M, entry))//走到出口就打印栈里面的数据
{
PrintPath(&path);
}
else
{
printf("没找到通路");
}
StackDestory(&path);
for (int i = 0; i < N; i++)//释放动态开辟的数组
{
free(maze[i]);
}
free(maze);
maze = NULL;
}
return 0;
}
迷宫最短路径求解
原题链接地下迷宫
题目分析
- 这个问题和上个问题类似,但是注意这里的墙换成0,能走的路换成了1,起点的位置是左上角,但是终点的位置是右下角。
- 这个问题增加了体力限制。一般这种类型的问题都是要求最短路径,也就是消耗体力最少的路径。
- 求最短路径只需要我们把所有的路径都求出来然后比较找出最短的即可。
- 由于要求最短路径,这也就意味着有的位置我们会走很多次,因此在回溯的时候我们需要把已经改成2的位置再改回1
- 其他地方和上一个题一样。
代码实现
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
#include<string.h>
//定义坐标的结构体
typedef struct Postion
{
int row;
int col;
}PT;
//
typedef PT STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
//初始化
void StackInit(ST* ps)
{
assert(ps);
ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL)
{
exit(-1);
}
ps->capacity = 4;
//top初始为0,top指向栈顶元素的下一个元素,top初识为-1,top指向栈顶的元素
ps->top = 0;
}
//销毁
void StackDestory(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
//入栈
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
if (tmp == NULL)
{
exit(-1);
}
else
{
ps->a = tmp;
ps->capacity *= 2;
}
}
ps->a[ps->top] = x;
ps->top++;
}
//出栈
void StackPop(ST* ps)
{
assert(ps);
ps->top--;
}
//取栈顶的数据
STDataType StackTop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->a[ps->top - 1];
}
//求数据的个数
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
//判断是否为空
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
//
ST path;//保存路径坐标
ST minpath;//保存最短路径的坐标
//输出栈里面的坐标路径
void PrintPath(ST* ps)
{
//将path的数据导入rPath
ST rPath;
StackInit(&rPath);
while (!StackEmpty(ps))
{
StackPush(&rPath, StackTop(ps));
StackPop(ps);
}
while (StackSize(&rPath) > 1)
{
PT top = StackTop(&rPath);
printf("[%d,%d],", top.row, top.col);
StackPop(&rPath);
}
PT top = StackTop(&rPath);
printf("[%d,%d]", top.row, top.col);
StackPop(&rPath);
StackDestory(&rPath);
}
//判断是否越界
bool IsPass(int** maze, int N, int M, PT 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 StackCooy(ST* ppath, ST* pcopy)
{
pcopy->a = (STDataType*)malloc(sizeof(STDataType) * ppath->capacity);
memcpy(pcopy->a, ppath->a, sizeof(STDataType) * ppath->top);
pcopy->top = ppath->top;
pcopy->capacity = ppath->capacity;
}
//实现走迷宫的函数,注意这里的类型改成void,因为路径不止一条
void GetMazePath(int** maze, int N, int M, PT cur, int p)
{
StackPush(&path, cur);//将坐标放到栈中
//如果走到出口
if (cur.row == 0 && cur.col == M - 1)
{
//如果体力大于等于0并且Path栈里面的坐标数量比minpath少,就将Path拷贝到minpath
if (p >= 0 && StackEmpty(&minpath)
|| StackSize(&path) < StackSize(&minpath))
{
StackDestory(&minpath);
StackCooy(&path, &minpath);
}
}
//探测cur上下左右四个方向
PT next;
//把行和列对应的位置置成2
maze[cur.row][cur.col] = 2;
//上
next = cur;
next.row -= 1;
if (IsPass(maze, N, M, next))
{
GetMazePath(maze, N, M, next, p - 3);
}
//下
next = cur;
next.row += 1;
if (IsPass(maze, N, M, next))
{
GetMazePath(maze, N, M, next, p);
}
//左
next = cur;
next.col -= 1;
if (IsPass(maze, N, M, next))
{
GetMazePath(maze, N, M, next, p - 1);
}
//右
next = cur;
next.col += 1;
if (IsPass(maze, N, M, next))
{
GetMazePath(maze, N, M, next, p - 1);
}
maze[cur.row][cur.col] = 1;//回溯的时候将已经走过的位置改成1
StackPop(&path);//出栈
}
int main()
{
int N = 0, M = 0, p = 0;//P代表体力
while (scanf("%d%d%d", &N, &M, &p) != EOF)//输入行和列以及体力
{
int** maze = (int**)malloc(sizeof(int*) * N);
for (int i = 0; i < N; i++)
{
maze[i] = (int*)malloc(sizeof(int) * M);
}
//二维数组的输入
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
scanf("%d", &maze[i][j]);
}
}
StackInit(&path);//初始化两个栈
StackInit(&minpath);
PT entry = { 0,0 };
GetMazePath(maze, N, M, entry, p);
if (!StackEmpty(&minpath))//如果minpath里面不为空说明已经有最短路径
{
PrintPath(&minpath);
}
else
{
printf("Can not escape!");
}
StackDestory(&path);//销毁栈
StackDestory(&minpath);
for (int i = 0; i < N; i++)//释放动态开辟的数组
{
free(maze[i]);
}
free(maze);
maze = NULL;
}
return 0;
}
注意,这里有个将Path栈里的数据拷贝到minpath里的操作,这里不能采用直接赋值minpath=Path
的方式,因为直接赋值是将minpath指向Path的空间,这样之前minpath的空间就会丢失,而且minpath和Path指向同一块空间,在销毁时就会销毁两次。
这里采用的方法是将minpath的空间销毁,然后再动态创建一个和Path同样大小的空间让minpath指向,最后将Path里面的数据用memcp函数拷贝到minpath中。