迷宫问题
用循环模拟压栈来实现迷宫问题
我们可以用一个二维数组(N*N)来表示迷宫,1表示路,0表示墙,我们把下方当成迷宫入口,出口为数组右方,此迷宫有两个出口。
需要建立一个结构体arg用来存放关于迷宫(二维数组)的信息。也就是存二维数组的两个下标
然后创建关于用来压栈的结构体
迷宫每个节点都有4个方向可以探寻。碰到墙壁(0)或者边界则此路不通
传迷宫的入口进去也就是数组gMaze[5][2]的信息,这时我们需要传5,2,然后再传一个整型用来标记迷宫的状态,用arg.att表示。
arg.att:这个状态用来标记我们是否试走过 此处 的四个方位,因为0是墙,1是路,所以我们用2来表示我们试过此处的左边能不能通行,3用来表示此处的上方是否通行,4表示右边,5表示下边。用6来表示出口。假如说试过左边不能走,我们就将标记改为3,让他继续试上边能不能走。
进入循环
进入循环之前,先将5,2以及初始标记值2压进栈。
迷宫出口条件:
结束条件为,如果目标走到数组右侧,也就是gMaze[ ][N-1]结束,然后跳出整个循环。
(如果在这里结束,我们则只能找到一条通路,我们让while循环来决定是否结束,此时便能找出多个出口)
先看下面的此处坐标各个方向能不能走,再看下面这段话:
保存每一条路:
如果他能走到这里就说明是一条通路,这时数组里的数据就是走过的一条路径,这时我们创建一个指针数组,每次走到这里,我们就动态开辟一块内存。复制这个数组到该内存中,然后让指针数组来保存这个内存地址,然后让指针数组的下标++,实现保存每一种通路求出最短路径:
我们动态开辟一块内存用来保存gMaze数组,一个变量k=0; 判断条件为(如果k==0 || 栈的大小 <=k) 我们就将此时的数组保存起来,然后将k更新为栈的大小值。
这样我们就会将第一次的数组保存起来,后面不断的将栈最小时候的数组替换到当前动态开辟的内存中,实现最短路径
保存栈顶元素
然后我们查看栈顶元素,也就是把当前的坐标,以及此处的状态复制到另一个argnext中
- 左边是否可行
先看左边能不能走
如果argnext.att<=2说明是刚进来,还没有试左边能不能走,因此进入判左语句中
左边能走我们,将数组该位置的值改为2,并且打印这个数组。
将argnext.x -= 1 后,判断这个点是不是墙、否越界数组,如果符合条件进入语句
进入语句就说明左边是能走的通的,然后我们将栈顶的也就是arg的状态arg.att改为3,说明我们已经走过左边,并且能走,
改过当前位置的状态之后,因为此处的左方是能走通的。因此我们需要将左方的坐标以及初始状态也压入栈内,
左边能走了,因为下面还有试其他方向的语句,我们让它每次只走一条路,因此我们需要continue来跳出本次循环,假如没有continue可能会使程序出错。
- 上边是否可行
试过左边之后,试上边能不能走
如果argnext.att<=3说明是已经试过左边,然后来试上边。
上边能走我们,将数组该位置的值改为3,并且打印这个数组。
将argnext.y -= 1 后,判断这个点是不是墙、否越界数组,如果符合条件进入语句
进入语句就说明上边是能走的通的,然后我们将栈顶的也就是arg的状态arg.att改为4,说明我们已经走过上边,并且能走,
改过当前位置的状态之后,因为此处的上方是能走通的。因此我们需要将上方的坐标以及初始状态也压入栈内。然后跳出本次循环。
- 右边是否可行
试过上边之后,试右边能不能走
如果argnext.att<=4说明是已经试过上边,然后来试右边。
右边能走我们,将数组该位置的值改为4,并且打印这个数组。
将argnext.y += 1 后,判断这个点是不是墙、否越界数组,如果符合条件进入语句
进入语句就说明右边是能走的通的,然后我们将栈顶的也就是arg的状态arg.att改为5,说明我们已经走过右边,并且能走,
改过当前位置的状态之后,因为此处的右方是能走通的。因此我们需要将上方的坐标以及初始状态也压入栈内。然后跳出本次循环。
- 下边是否可行
试过右边之后,试下边能不能走
如果argnext.att<=5说明是已经试过右边,然后来试下边。
右边能走我们,将数组该位置的值改为5,并且打印这个数组。
将argnext.x += 1 后,判断这个点是不是墙、否越界数组,如果符合条件进入语句
进入语句就说明下边是能走的通的,然后我们将栈顶的也就是arg的状态arg.att改为6,说明我们已经走过下边,并且能走,
改过当前位置的状态之后,因为此处的下方是能走通的。因此我们需要将上方的坐标以及初始状态也压入栈内。然后跳出本次循环。
- 出栈
如果能走到最后一步说明这个点的上下左右都不能走,然后我们需要将它的状态置为1,也就是说,把它改成我们没走过的,这样就不影响后面寻找多条路时候我们不能再次走到这里的问题。
然后将这个点Pop出栈。
循环结束条件
这样构成一个循环,结束条件为栈为空,结束条件为栈为空时,我们可以寻找迷宫的多个出口。
每次在循环中打印数组时,我门用一个system(“cls”);来清屏,实现一个看起来像是能动的迷宫(哈哈哈)
代码实现
- 下面是代码实现
#pragma once
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>
#include <stdio.h>
#define MAX_ 100
#define ROW 6
#define COL 6
//坐标结构体
typedef struct Pos
{
int x;
int y;
}Pos;
//坐标,当前位置状态
typedef struct Arg
{
Pos pos;
int att;
}Arg;
//栈
typedef struct Stack
{
Arg arr[MAX_];
int size;
}Stack;
void Print();
Stack stack;
//迷宫
int gMaze[ROW][COL] = {
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 1 },
{ 0, 0, 1, 0, 1, 0 },
{ 0, 0, 1, 1, 1, 1 },
{ 0, 0, 1, 0, 0, 0 }
};
//保存每次路径的指针数组
int *gSuccess[ROW*COL];
int gSize = 0;
//保存最短路径的指针
int *min = NULL;
int k = 0;
//定义一个符号数组来代替迷宫的1,2,3,4,5,6,
//看起来能直观一点
static const char *symbols[] = {
"█",
" ",
"←",
"↑",
"→",
"↓",
"◇"
};
//初始化栈
void Init(Stack *stack)
{
assert(stack);
stack->arr[stack->size].pos.x = 0;
stack->arr[stack->size].pos.y = 0;
stack->arr[stack->size].att = 0;
stack->size = 0;
}
//压栈
void Push(Stack *stack, Arg arg, int att)
{
assert(stack);
assert(stack->size < MAX_);
stack->arr[stack->size].pos.x = arg.pos.x;
stack->arr[stack->size].pos.y = arg.pos.y;
stack->arr[stack->size].att = att;
stack->size++;
}
//出栈
void Pop(Stack *stack)
{
assert(stack);
assert(stack->size >0);
stack->size--;
}
//初始化
void Destroy(Stack *stack)
{
assert(stack);
stack->size = 0;
}
//栈大小
int Size(Stack *stack)
{
return stack->size;
}
//栈是否为空
int IsEmpty(Stack *stack)
{
return stack->size == 0 ? 1 : 0;
}
//栈顶元素
Arg Top(Stack *stack)
{
return stack->arr[stack->size-1];
}
//此位置是否通行
int CheckIsAccess(int x, int y)
{
if (gMaze[x][y] == 1)
{
return 1;
}
else {
return 0;
}
}
//清屏,并且改变数组值为当前位置状态
void SetAndPrint(int x,int y, int att)
{
system("cls");
gMaze[x][y] = att;
Print(gMaze);
Sleep(50);
}
//改变栈内,当前坐标状态
void UpdateTop(Stack *stack, int data)
{
stack->arr[stack->size-1].att = data;
}
//打印
void Print(int (*gMaze)[COL])
{
int i, j;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
printf("%s", symbols[gMaze[i][j]] );
}
printf("\n");
}
}
//主循环体
void GoMazeRecirsion(Pos pos)
{
Arg arg;
Arg argnext;
arg.pos = pos;
arg.att = 2;
Push(&stack, arg, 2);
while (!IsEmpty(&stack))
{
arg = Top(&stack);
if (arg.pos.y == COL - 1)
{
SetAndPrint(arg.pos.x, arg.pos.y, 6);
//开辟一块内存保存每次的数组
int *copy = (int *)malloc(sizeof(int)*COL*ROW);
assert(copy);
memcpy(copy, gMaze, sizeof(int)*COL*ROW);
gSuccess[gSize++] = copy;
//开辟一块内存用来保存最短路径
int *MIN = (int *)malloc(sizeof(int)*COL*ROW);
assert(MIN);
if(k==0 || Size(&stack) < k)
{
memcpy(MIN, gMaze, sizeof(int)*COL*ROW);
min = MIN;
k = Size(&stack);
}
}
argnext = arg;
//左边能不能走
if (argnext.att <= 2)
{
SetAndPrint(arg.pos.x, arg.pos.y, 2);
argnext = arg;
argnext.pos.y -= 1;
if (argnext.pos.y >= 0 && CheckIsAccess(argnext.pos.x, argnext.pos.y))
{
UpdateTop(&stack, 3);
Push(&stack, argnext, 2);
continue;
}
}
//上边能不能走
if (argnext.att <= 3)
{
SetAndPrint(arg.pos.x, arg.pos.y, 3);
argnext = arg;
argnext.pos.x -= 1;
if (argnext.pos.x >= 0 && CheckIsAccess(argnext.pos.x, argnext.pos.y))
{
UpdateTop(&stack, 4);
Push(&stack, argnext, 2);
continue;
}
}
//右边能不能走
if (argnext.att <= 4)
{
SetAndPrint(arg.pos.x, arg.pos.y, 4);
argnext = arg;
argnext.pos.y += 1;
if (argnext.pos.y <= COL - 1 && CheckIsAccess(argnext.pos.x, argnext.pos.y))
{
UpdateTop(&stack, 5);
Push(&stack, argnext, 2);
continue;
}
}
//下边能不能走
if (argnext.att <= 5)
{
SetAndPrint(arg.pos.x, arg.pos.y, 5);
argnext = arg;
argnext.pos.x += 1;
if (argnext.pos.x <= ROW - 1 && CheckIsAccess(argnext.pos.x, argnext.pos.y))
{
UpdateTop(&stack, 6);
Push(&stack, argnext, 2);
continue;
}
}
gMaze[arg.pos.x][arg.pos.y] = 1;
Pop(&stack);
}
printf("没有通路了\n");
}
//主函数体
void test()
{
int i;
Init(&stack);
Pos pos = {5, 2};
GoMazeRecirsion(pos);
printf("有%d种方法\n",gSize);
for (i = 0; i < gSize; i++)
{
Print((int(*)[COL])gSuccess[i]);
}
printf("最短路径\n");
Print((int(*)[COL])min);
//别忘记释放内存
for (i = 0; i < gSize; i++)
{
free(gSuccess[i]);
}
free(min);
min = NULL;
gSuccess[gSize] = NULL;
system("pause");
}
#include "MIGONG.h"
//主函数
int main()
{
test();
return 0;
}