迷宫问题--压栈、回溯、全部路径、最短路径

迷宫问题

用循环模拟压栈来实现迷宫问题

  • 我们可以用一个二维数组(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;
}

运行结果

这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_41804778/article/details/80623053