一、 分析
- 回溯法
对一个包括有很多个结点,每个结点有若干个搜索分支的问 题,把原问题分解为若干个子问题求解的算法;当搜索到某个结点发 现无法再继续搜索下去时,就让搜索过程回溯(回退)到该节点的前一 个结点,继续搜索该节点外的其他尚未搜索的分支;如果发现该结点 无法再搜索下去,就让搜索过程回溯到这个结点的前一结点继续这样 的搜索过程;这样的搜索过程一直进行到搜索到问题的解或者搜索完 了全部可搜索分支没有解存在为止。
- 分类
迷宫问题可以分为三种类型
- 简单迷宫
- 多通路迷宫(通路中不带环)
- 环路迷宫(通路中带环)
迷宫的规定:
(0是墙,路是1,走过用2标记,入口点规定为(x=5,y=2),走的方向: 左 -> 上 -> 右 -> 下 )
对于简单迷宫可以运用(栈+循环),也可以用递归
对于无环路的多通路迷宫可以运用(栈+循环),也可以用递归
对于有环路的多通路迷宫用递归,回溯之前将走过的路重新置为通路
- 扩展
在递归的前提下找最短通路:
1.递归+栈,递归回溯,保存路径栈
2.栈拷贝,和之前的最短路径作对比
二、顺序栈的基本操作(Stack.h)
初始化、销毁、增删改、查询栈顶元素、栈的元素的个数、栈满、栈空
#include <stdio.h>
#include <assert.h>
#include <string.h>
typedef Position StackDataType;
#define MAX_SIZE (100)
typedef struct Stack {
StackDataType array[MAX_SIZE];
int top; // 表示当前个数
} Stack;
// 初始化/销毁
// 增(只能从栈顶)/删(只能删除栈顶)/查(只能查看栈顶元素)
// 个数 / 是否空 / 是否满
// 增 -> 顺序表的尾插
// 删 -> 顺序表的尾删
void StackInit(Stack *pStack)
{
pStack->top = 0;
}
void StackDestroy(Stack *pStack)
{
pStack->top = 0;
}
void StackPush(Stack *pStack, StackDataType data)
{
assert(pStack->top < MAX_SIZE);
pStack->array[pStack->top++] = data;
}
void StackPop(Stack *pStack)
{
assert(pStack->top > 0);
pStack->top--;
}
StackDataType StackTop(const Stack *pStack)
{
assert(pStack->top > 0);
return pStack->array[pStack->top - 1];
}
int StackSize(const Stack *pStack)
{
return pStack->top;
}
int StackFull(const Stack *pStack)
{
return pStack->top >= MAX_SIZE;
}
int StackEmpty(const Stack *pStack)
{
return pStack->top <= 0;
}
void StackCopy(Stack *pDest, Stack *pSrc) //栈拷贝
{
memcpy(pDest->array, pSrc->array, sizeof(StackDataType)* pSrc->top);
pDest->top = pSrc->top;
}
三、栈+循环实现简单迷宫
1.利用栈实现回溯
2.走过的路需要标记出来
3.找到出口后输出出口位置,程序退出
4.判断是否可以通过(a.是否越界 b.是否路是1)
- 用来保存迷宫中的坐标
#define ROWS (6)
#define COLS (6)
// 坐标方向和平时不太一样,x 朝下,y 朝右
typedef struct {
int x;
int y;
} Position;
// 入口点
Position gEntry = { 5, 2 };
- 判断是否走到出口,最后一列都是出口
int IsExit(Position pos)
{
if (pos.y == COLS - 1) {
return 1;
}
else {
return 0;
}
}
- 判定是否可以走
int CanPass(Position pos)
{
if (pos.x >= ROWS) {
return 0;
}
if (pos.y >= COLS) {
return 0;
}
return gMaze[pos.x][pos.y] == 1;
}
- 打印通路坐标
void PrintPath(Stack *pStack)
{
Position at;
for (int i = 0; i < pStack->top; i++) {
at = pStack->array[i];
printf("x = %d, y = %d\n", at.x, at.y);
}
}
- 实现迷宫
void RunMaze()
{
// 需要一个栈,实现回溯
Stack stack;
StackInit(&stack);
Position at;
Position next;
at.x = gEntry.x;
at.y = gEntry.y;
while(1)
{
//标记位置
gMaze[at.x][at.y] = 2;
//压栈,为回溯做准备
StackPush(&stack, at);
//判断是否走出
if (IsExit(at))
{
PrintPath(&stack);
return;
}
// 根据 左 -> 上 -> 右 -> 下 来尝试
next.x = at.x;
next.y = at.y - 1;
if (CanPass(next))
{
at.x = next.x;
at.y = next.y;
continue;
}
next.x = at.x - 1;
next.y = at.y;
if (CanPass(next))
{
at.x = next.x;
at.y = next.y;
continue;
}
next.x = at.x;
next.y = at.y + 1;
if (CanPass(next))
{
at.x = next.x;
at.y = next.y;
continue;
}
next.x = at.x + 1;
next.y = at.y;
if (CanPass(next))
{
at.x = next.x;
at.y = next.y;
continue;
}
//无法前行,开始回溯
StackPop(&stack); // 这里 pop 的是当前的 at
if (StackEmpty(&stack)) {
printf("没有出口\n");
return;
}
at = StackTop(&stack);
StackPop(&stack);
}
}
四、用递归方法实现
递归尝试的方向不会重新尝试
- 图形界面打印
void PrintMaze()
{
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
if (gMaze[i][j] == 0) {
printf("█");
}
else if (gMaze[i][j] == 1) {
printf(" ");
}
else if (gMaze[i][j] == 2) {
printf("⊕");
}
}
printf("\n");
}
printf("\n\n");
}
- 最短路径
//看最小路径中的元素个数
int lpath = StackSize(&min);
- 实现迷宫
//定义为全局变量
Stack path; //当前路径
Stack min; //最小路径
void RunMazeRec(Position at)
{
Position next;
StackPush(&path, at);
// 标记走过位置
gMaze[at.x][at.y] = 2;
PrintMaze();
if (IsExit(at))
{
// 如果当前路径(path)小于之前的最小路径(min),则当前路径是最短
if (StackEmpty(&min) || StackSize(&path) < StackSize(&min)) {
StackCopy(&min, &path);
}
PrintPath(&path);
printf("============================\n");
// 重新置为 1
gMaze[at.x][at.y] = 1;
StackPop(&path);
return; // 会发生回溯
}
// 根据 左 -> 上 -> 右 -> 下 来尝试
next.x = at.x;
next.y = at.y - 1;
if (CanPass(next)) {
RunMazeRec(next);
PrintMaze();
}
next.x = at.x - 1;
next.y = at.y;
if (CanPass(next)) {
RunMazeRec(next);
PrintMaze();
}
next.x = at.x;
next.y = at.y + 1;
if (CanPass(next)) {
RunMazeRec(next);
PrintMaze();
}
next.x = at.x + 1;
next.y = at.y;
if (CanPass(next)) {
RunMazeRec(next);
PrintMaze();
}
// 重新置为 1
gMaze[at.x][at.y] = 1;
StackPop(&path);
return; // 回溯
}
五、测试用例 gMaze[ROWS][COLS]
//简单迷宫
int gMaze[ROWS][COLS] = {
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 0 },
{ 0, 0, 1, 0, 1, 1 },
{ 0, 0, 1, 0, 0, 0 }
};
//无环多通路
int gMaze[ROWS][COLS] = {
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 1 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 1 },
{ 0, 0, 1, 0, 0, 0 }
};
//多通路环路迷宫
int gMaze[ROWS][COLS] = {
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 0 },
{ 0, 0, 1, 0, 1, 0 },
{ 0, 0, 1, 0, 1, 0 },
{ 0, 0, 1, 1, 1, 1 },
{ 0, 0, 1, 0, 0, 0 }
};