迷宫问题_C++

1 篇文章 0 订阅
1 篇文章 0 订阅

Description
有一个 10 x 10 的迷宫,起点是‘S’,终点是‘E’,墙是‘#’,道路是空格。一个机器人从起点走到终点。当机器人走到一个通道块,前面已经没有路可走时,它会转向到当前面向的右手方向继续走。如果机器人能够过,则留下足迹‘*’,如果走不通,则留下标记‘!’。
下面给出书中的算法,请你模拟机器人的走法输出最终的状态。
Input
一个 10 x 10 的二维字符数组。
Output
机器人走过的路径状态。

在这里插入图片描述
在这里插入图片描述
先给出自己实现的代码:

#include<iostream>
#include<stdio.h>
#define Stack_size 100
#define No_in_Stack -1
#define In_Stack 0
#define Finish 1
using namespace std;
int flag=0;
typedef struct MAZE
{
    char c;
    int Count;
    int backx;
    int backy;
    int flag;

}M;
typedef struct S
{
    char Stack_obj[Stack_size];
    int Stack_pos[Stack_size][2];
    int top;
}Seq_Stack;
void InitStack(S *s)
{
    s->top=-1;
}
int Push(S *s,char c,int m,int n)
{
    if(s->top==Stack_size-1)
        return false;
    (s->top)++;
    s->Stack_obj[s->top]=c;
    s->Stack_pos[s->top][0]=m;
    s->Stack_pos[s->top][1]=n;
    return true;
}
int Pop(S *s)
{
    if(s->top==-1)
        return false;
    int c;
    //c=s->Stack_obj[s->top];

    (s->top)--;
    return s->top;
}
int GetTop(S s)
{
    if(s.top==-1)
        return false;
    else
        return s.Stack_obj[s.top];
}
void ClearStack(S *s)
{
    s->top==-1;
}
bool IsEmpty(S s)
{
    if(s.top==-1)
        return true;
    else
        return false;
}
bool IsFull(S s)
{
    if(s.top==Stack_size-1)
        return true;
    else
        return false;
}
void Show(char maze[][11])
{
    for(int i=0;i<10;i++)
    {
        cout<<maze[i]<<endl;
    }
}
void recursion_Go(char maze[][11],int m,int n)
{
        if(maze[m][n]=='E')
        {
            flag=Finish;
            maze[m][n]='*';
            return ;
        }
        //cout<<"flag:   "<<flag<<endl;;
        if((maze[m][n+1]==' '||maze[m][n+1]=='E'))
        {
            maze[m][n]='*';
            recursion_Go(maze,m,n+1);
            if(flag==Finish)
                return ;
        }
        if((maze[m+1][n]==' '||maze[m+1][n]=='E'))
        {
            maze[m][n]='*';
            recursion_Go(maze,m+1,n);
            if(flag==Finish)
                return ;
        }
        if((maze[m][n-1]==' '||maze[m][n-1]=='E'))
        {
            maze[m][n]='*';
            recursion_Go(maze,m,n-1);
            if(flag==Finish)
                return ;
        }
        if((maze[m-1][n]==' '||maze[m-1][n]=='E'))
        {
            maze[m][n]='*';
            recursion_Go(maze,m-1,n);
            if(flag==Finish)
                return ;
        }
        maze[m][n]='!';
}
void non_recursion_Go(char maze[][11],int m,int n)
{
    int p=0;
    S s;
    InitStack(&s);
    M Maze[10][11];
    for(int i=0;i<10;i++)
    {
        for(int j=0;j<11;j++)
        {
            Maze[i][j].c=maze[i][j];
            Maze[i][j].Count=0;
            Maze[i][j].backx=0;
            Maze[i][j].backy=0;
            Maze[i][j].flag=No_in_Stack;
        }
    }
    char c;
    int top;
    int a,b;
    Push(&s,Maze[m][n].c,m,n);
    Maze[m][n].flag=In_Stack;
    while(!IsEmpty(s))
    {
        top=Pop(&s);
        //p++;
        c=s.Stack_obj[top+1];
        a=s.Stack_pos[top+1][0];
        b=s.Stack_pos[top+1][1];
        //cout<<p<<' '<<a<<' '<<b<<' '<<top<<endl;
        Maze[a][b].c='*';
        //Maze[a][b].flag=p;
        if(c==' '||c=='S')
        {
            Maze[a][b].c='*';
            if(Maze[a-1][b].c==' '||Maze[a-1][b].c=='E')
            {
                if(Maze[a-1][b].flag==No_in_Stack)
                {
                    Push(&s,Maze[a-1][b].c,a-1,b);
                    Maze[a][b].Count++;
                    Maze[a-1][b].backx=a;
                    Maze[a-1][b].backy=b;
                    Maze[a-1][b].flag=In_Stack;
                }
            }
            if(Maze[a][b-1].c==' '||Maze[a][b-1].c=='E')
            {
                if(Maze[a][b-1].flag==No_in_Stack)
                {
                    Push(&s,Maze[a][b-1].c,a,b-1);
                    Maze[a][b].Count++;
                    Maze[a][b-1].backx=a;
                    Maze[a][b-1].backy=b;
                    Maze[a][b-1].flag=In_Stack;
                }
            }
            if(Maze[a+1][b].c==' '||Maze[a+1][b].c=='E')
            {
                if(Maze[a+1][b].flag==No_in_Stack)
                {
                    Push(&s,Maze[a+1][b].c,a+1,b);
                    Maze[a][b].Count++;
                    Maze[a+1][b].backx=a;
                    Maze[a+1][b].backy=b;
                    Maze[a+1][b].flag=In_Stack;
                }
            }
            if(Maze[a][b+1].c==' '||Maze[a][b+1].c=='E')
            {
                if(Maze[a][b+1].flag==No_in_Stack)
                {
                    Push(&s,Maze[a][b+1].c,a,b+1);
                    Maze[a][b].Count++;
                    Maze[a][b+1].backx=a;
                    Maze[a][b+1].backy=b;
                    Maze[a][b+1].flag=In_Stack;
                }
            }
        }
        if(c=='E')
        {
            Maze[a][b].c='*';
            break;
        }
        for(int i=0;i<10;i++)
        {
            for(int j=0;j<11;j++)
            {
                if(Maze[i][j].c=='*' && Maze[i][j].Count==0)
                {
                    Maze[Maze[i][j].backx][Maze[i][j].backy].Count--;
                    Maze[i][j].c='!';
                }
            }
        }
        for(int i=0;i<10;i++)
        {
            for(int j=0;j<11;j++)
            {
                if(Maze[i][j].Count==0 &&Maze[i][j].c=='*')
                {
                    Maze[i][j].c='!';
                }
            }
        }
//        for(int i=0;i<10;i++)
//        {
//            for(int j=0;j<10;j++)
//            {
//                cout<<Maze[i][j].Count;
//            }
//            cout<<endl;
//        }
    }
    for(int i=0;i<10;i++)
    {
        for(int j=0;j<10;j++)
        {
            maze[i][j]=Maze[i][j].c;
            //cout<<Maze[i][j].flag<<' ';
        }
        //cout<<endl;
    }
}
int main()
{
    char maze[10][11];
    for(int i=0;i<10;i++)
    {
        gets(maze[i]);
        //getchar();
    }
    int a,b;
    for(int i=0;i<10;i++)
    {
        for(int j=0;j<10;j++)
        {
            if(maze[i][j]=='S')
            {
                a=i;
                b=j;
            }
        }
    }
    //recursion_Go(maze,a,b);
    non_recursion_Go(maze,a,b);
    Show(maze);
    return 0;
}

对于题干的思路,要先捋清楚,到底是怎么走的。在这个题目中,分析知,机器人会优先往右走,如果右边走不通了,会选择向下走,其次再是往左,最后的选择是往上。(→ ↓ ← ↑)这也就是说,不管是有 墙挡住,还是说遇到了分岔路口,都会以这个优先级去选择路径,这就比没有优先级,让你找最佳路径要更有思路一些。这个题目我用了非递归(显式栈)和递归(隐式栈)两种思路去完成。并对一些代码的解读进行备注,以便后续回顾复习。

1.首先看到这个优先级以及根据题目要求使用栈去实现,便联想到了深度优先搜索算法。对于某一个结点,会找到它所联系的结点。对于本题来说,和一个结点有关的无非就是其上下左右四个结点了,又由于输入样例过于显眼,就会想到用数组去完成。也就是说,在进行遍历其相邻结点的过程中,会考虑的第一个问题是,这4个结点有没有溢出(边缘的结点)?思考过后发现并不用额外花费时间去考虑这一部分,因为所以的可行结点均在迷宫这个外围全是#的内部,不会存在下标越界的情况。

2.关于迷宫数组

    char maze[10][11];
    for(int i=0;i<10;i++)
    {
        gets(maze[i]);
        //getchar();
    }

明明是个10✖10的矩阵,为什么第二维不设定成10呢?我先考虑了第一个问题,如何把’ '(空格)存到char数组中。知道对于c++的char数组,空格他是不会计入的,那么我想到了运用c中学到的gets函数,去计入空格,那么对于gets函数麻烦的一点除了要添加头文件以外,就是这个回车换行的操作了。如果进行多次的gets,一定要用getchar()去缓冲我们敲击的回车符。在简单的进行了getchar()的使用后,发现放在哪里好像都不好用,于是干脆果断一点,将回车符也计入数组中不就可以回避这个问题了吗,正好一行字符数组输入完毕,他会自动换行,而在输入格式上,我们也看的很舒服了,因此就额外加了一个空间,去放我们输入时,为了格式工整而加上的回车符。

3.关于递归函数
递归函数对于我们而言是非常友好的,会替我们剩下不少的脑子,所以在写递归函数的时候是相对来说轻松一点的,但我在一步一步探索中,也存在了些许问题。以其中一个为例(因为都是类似的操作)

        if((maze[m][n+1]==' '||maze[m][n+1]=='E'))
        {
            maze[m][n]='*';
            recursion_Go(maze,m,n+1);
            if(flag==Finish)
                return ;
        }

代码很好理解,但是在一开始,我并没有加flag这个判断位,导致了即使我找到了终点E,但他仍会继续走下去,直到走不了了为止。我之前的想法是,只用在判断是否等于E了,如果等于就return。就够了,但是后通过逻辑推理和尝试,发现有问题:假设我们一直往右走,不断深入递归这个函数,找到了终点E,那么也只有最后一层(找到E的那一层),会进行return操作。进而回到了倒数第二层,执行了下一个if(往下走)的递归中。我们应该杜绝这种情况,所以应该在每层递归返回之后的接下来的语句,不需要继续执行了,就返回一次。因此就有了这个判断位的诞生。有个小插曲就是,判断位我在这里没有定义在递归函数中,因为每次递归后,都会被覆盖重置,而如果定义在main函数里,又得当参数去传到递归函数才行。于是我选择定义一个全局变量去记录是否结束递归。另外,递归的过程其实实质上和深度优先搜索是一样的,都会根据程序的顺序执行,先调用第一个递归,进去之后还是第一个递归……

4.关于非递归函数
非递归函数我卡了很久,可能突然捡起来忘的差不多了的数据结构,导致脑子有点转不过来。我在这里把我遇到的坎一一写出来,如果以后我遇到类似的问题,可以很好的查阅。
1)最直截了当的,什么都不考虑的,非递归就是一个入栈出栈加循环嘛。因此和递归函数写的时候略有不同,栈是先进后出的数据结构,因此我们在入栈的时候,一定要反着入栈,也就是按照上左下右(↑ ← ↓ → )的顺序入栈,这样才能保证出栈的顺序是题目所要求的顺序。同时,由于是简单的数组结构,栈的定义及函数实现比较简单(有一个函数是略作修改的,这个下面再说)
2)只要入栈出栈的次序不弄错,这个时候是可以按题目要求,走完所有机器人到了的结点的。但是题目没有这么简单,他要求你把不能走通的结点,用’!‘给标识出来,那么我们就得进行判断,如何回溯。这里和深度优先搜索不同:如果这条支路没有找到结点,我们可以直接换条支路(DFS)。但是在这里,如果一条支路没有找到出口E,那么这整条路就应该被标识为’!’。简单的是,如果这条路的最后一个结点不是E,它就被标识为’!’,但这并没有结束。因为如果是单路,那么整条路都应该走不通,如果是路口,那么应该该支路走不通,转而选下一个分支去走。这样看来,每个结点,都应该有个判断位,判断该结点,是否走得通,因此我在这里选择了使用Count去计算,因为一个结点最多也只和4个结点有关联,如果有一个能走,就Count++,从而所有途径的结点,都各自有了一个Count值。如果这个值不为,就说明还有路可走。
关于这个结构体我也是想了很久,如何去扩展我原本的什么都没有的迷宫数组的需求。发现创建其他的数组都没有很强的一一对应性。索性就想到了将maze数组包含进来,构造一个更大的数据结构MAZE,这样每个元素都可以被一一对应起来了,我只需要创建一个MAZE类型的二维数组,将原maze数组赋值进来,最后运行完后再反赋值回去即可。这样就达到了在过程中的扩展功能的效果。

typedef struct MAZE
{
    char c;
    int Count;
    int backx;
    int backy;
    int flag;
}M;

那么我们继续分析,我们是通过一个结点出栈,从而和他有关联的结点入栈。那么经过多重操作,很有可能我们这条支路的首结点已经找不到了(出栈的结点并未被保存),但是这条路确确实实又走不通,需要回溯。这时,该怎么办?介于学过双向链表,所以我在这里借用了双向链表的思想,对于每个结点,我定义了两个整型变量去记录其上个结点BACK(BACK结点为这样一个结点:BACK出栈,该结点是他的关联结点,入栈)的位置。这样一来,我们可以通过遍历,从而找到其上个结点,上上个结点等。
3)写到这里其实大致的已经差不多了,剩下的就是对个别结点以及特殊结点的处理。现在来考虑一个问题:同一个结点,是不是只和一个结点有联系?答案是否定的,按照逻辑,一个结点能连接4个结点,那么一个结点也能同时被4个结点所连接。也就是说,可能当前结点,我在处理A结点的时候,已经入栈了,那么在处理B结点的时候,可能又会再次入栈。(当然,从结果来看,这是不影响的,因为处理过的结点第二次处理的时候肯定是直接返回)我在考虑这个问题的时候,是在输出flag数组的时候发现的,我想通过整型p去记录我当前出栈的结点是哪一个,从而方便逻辑分析和找错。按理说,p应该是顺序的数字,不存在间隔。但是中途却出现了间隔,因为同一个结点被入栈了两次,第一次出栈的时候赋给了一个p,第二次的出栈的时候,p被新的p覆盖了。所以我考虑了利用flag位,去记录我现在需要处理的结点,是否已经在栈中(flag==In_Stack),从而避免了重复入栈的情况。
4)那么如何处理上述两个问题呢?处理当前结点的Count,如果为0,则他的父结点的Count–(意味着少一种可能性的出路),这也只能处理到两个结点,如果父结点的父结点也不能走通,那么如何反馈到更高层呢?

        for(int i=0;i<10;i++)
        {
            for(int j=0;j<11;j++)
            {
                if(Maze[i][j].c=='*' && Maze[i][j].Count==0)
                {
                    Maze[Maze[i][j].backx][Maze[i][j].backy].Count--;
                    Maze[i][j].c='!';
                }
            }
        }
        for(int i=0;i<10;i++)
        {
            for(int j=0;j<11;j++)
            {
                if(Maze[i][j].Count==0 &&Maze[i][j].c=='*')
                {
                    Maze[i][j].c='!';
                }
            }
        }

第一个循环体的作用,可以看出来,是检索数组中,Count为0的结点,将其标位’!’,意为不可达,然后将其父结点的Count–,而第二个循环体的作用,是检索数组,Count为0的结点,将其标位’!’。可以看出来其实两个数组的目的很相似,那么为什么要用两次呢?(我没试过只用一次怎么解决)第一次的目的,主要是为了遍历,将每一个Count为0的结点的父结点进行减一操作,如果父结点为0了,则其父结点减一(但是好像这个双重循环也只是遍历了一遍数组,并没有在修改过Count后再遍历,加上第二个双重循环,也只能更新该结点和该结点的父结点两重关系,如果更多重了也没有解决。目前的思路是将判断语句if写成递归函数,实事递归调用,一直沿着父结点去判断,这样更合理和准确。但是现在这样也能通过,可能样例没有这么复杂)。第二次的目的,是为了将所以Count为0的结点,标记为’!’,表示走不通。如果在第一次中不及时的将当前结点设置为’!‘的话,会导致在循环的时候可能多次被访问,使其父结点的Count减为负。所以两个循环里,都要标记’!’。
5)关于特殊函数
上述说到了,在实现栈的相关函数的时候,我修改了其中几个函数,使其特殊化。

int Pop(S *s)
{
    if(s->top==-1)
        return false;
    int c;
    //c=s->Stack_obj[s->top];

    (s->top)--;
    return s->top;
}

以往的Pop()函数只是返回栈顶元素并移动栈顶指针减一就行。但是对于本题来说,出栈的元素只是单纯的字符,并没有其它的含义。这样导致的结果是,我无法得知这个元素是谁入栈后出栈的,进而无法确定入栈的结点。也就是如果不找到这个元素到底是属于哪个结点,就无法将结点间的联系构建起来。因此我选择了return top,返回栈顶指针。由于栈顶指针top肯定会减一,因此我们所需要的结点其实是在top+1这个结点里存放着的(出栈不等于删除结点,只是栈顶指针变化了,数据仍存在刚刚的位置)。所以得到了top之后,便可以通过以下代码,定位到具体对应的结点。

		top=Pop(&s);
        c=s.Stack_obj[top+1];
        a=s.Stack_pos[top+1][0];
        b=s.Stack_pos[top+1][1];

其结构体:(栈包含一个值数组,以及一个坐标(x,y)的二维数组,和栈顶指针top)

typedef struct S
{
    char Stack_obj[Stack_size];
    int Stack_pos[Stack_size][2];
    int top;
}Seq_Stack;

那么理所当然的,我在Push的时候,自然也得把这些信息(值,位置)存入到栈中才可以,于是顺其自然的就有了:

int Push(S *s,char c,int m,int n)
{
    if(s->top==Stack_size-1)
        return false;
    (s->top)++;
    s->Stack_obj[s->top]=c;
    s->Stack_pos[s->top][0]=m;
    s->Stack_pos[s->top][1]=n;
    return true;
}

最后,提供样例的输入和输出(直接复制粘贴排版很奇怪,题目又是图片形式。就放在代码注释里给出了,毕竟新手小白…)

/*
Sample Input
##########
#S #   # #
#  #   # #
#    ##  #
# ###    #
#   #    #
# #   #  #
# ### ## #
##      E#
##########
Sample Output
##########
#**#!!!# #
# *#!!!# #
#**!!##  #
#*###    #
#***#    #
# #***#  #
# ###*## #
##   ****#
##########
*/
  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值