1. 程式人生 > >棧的應用--迷宮問題

棧的應用--迷宮問題

問題描述:給定一個迷宮,給定入口和出口,找到從入口到出口的一條路徑(任何一條路徑都可以),迷宮為0表示可走,為1表示牆。用1將迷宮圍起來避免邊界問題。

實現思路:1.DFS搜尋(遞迴)

2.採用的資料結構

下面分別用這兩種方法來解決這個問題。

DFS搜尋(即遞迴+回溯)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#define ROW 9
#define COL 9

int integer[ROW][COL]={     //表示迷宮
{1,0,1,1,1,1,1,1,1}, {1,0,1,1,1,0,0,0,1}, {1,0,0,0,0,0,1,0,1}, {1,0,1,1,1,0,1,0,1}, {1,0,1,0,0,0,1,0,1}, {1,0,1,1,1,0,1,0,1}, {1,0,0,0,0,1,1,0,1}, {1,0,1,1,1,1,1,0,0}, {1,1,1,1,1,1,1,1,1} }; /*int integer[ROW][COL]={ {2,0,2,2,2,2,2,2,2,2},//1 {2,0,0,2,0,0,0,2,0,2},//2 {2,0,0,2,0,0,0,2,0,2},//3 {2,0,0,0,0,2,2,0,0,2},//4 {2,0,2,2,2,0,0,0,0,2},//5 {2,0,0,0,2,0,0,0,0,2},//6 {2,0,2,0,0,0,2,0,0,2},//7 {2,0,2,2,2,0,2,2,0,2},//8 {2,2,0,0,0,0,0,0,0,0},//9 {2,2,2,2,2,2,2,2,2,2} //10 }; 大家可以用這個迷宮進行再次觀察*/
int print(int integer[ROW][COL],int x,int y);//列印該迷宮 int dir[4][2]={ {1,0},{-1,0}, {0,1},{0,-1}, } ; //方向陣列,代表 4 個方向 int visted[120][120] ; // 1 代表訪問過 0 代表沒有訪問過 int check(int x,int y) //檢查下一步是否越界和是否已經走過以及是否是牆 { if(x< 0 || y<0 || x>= ROW || y>= COL) return 0; if(visted[x][y]) return
0; if(integer[x][y] != 0 ) return 0; return 1; } int dfs(int x,int y) //已經踏到了 x , y ,即x,y 可踏 { int xx,yy ,i ; usleep(100000); printf("\033c"); print(integer,x,y); if(x == 7 && y == 8 ) exit(0); for(i= 0;i< 4 ;i++) // 4 個方向 { xx = x + dir[i][0]; yy = y + dir[i][1]; if(check(xx,yy)) //xx ,yy 可踏上去 { visted[xx][yy]= 1; dfs(xx,yy) ; visted[xx][yy] = 0 ; //回溯 } } usleep(100000); //再次列印,顯示回溯的效果 printf("\033c"); print(integer,x,y); return 0; } int print(int integer[ROW][COL],int x,int y) { int i,j; for(i=0;i<ROW ;i++) { for(j=0 ;j<COL ;j++) { if(visted[i][j]) printf("\033[41;32m * \033[0m") ; else printf(" %d ",integer[i][j]); } printf("\n\n"); } } int main(void) { int i,j ; memset(visted,0,sizeof(visted)); visted[0][1]=1; //從入口出發 dfs(0,1) ; }

執行截圖:

這裡寫圖片描述

PS 1.這是一個動態演示的程式,可以清晰的看到移動的動作,所以執行有奇效

2. 回溯之後要再列印一次,才能有回溯的效果,並且必須有sleep 函式,否則會因為程式執行太快而導致看不到回溯的效果。

3. 如果對於DFS搜尋還不太懂的–>點這裡,文中提到的馬踏棋盤我會在下一篇部落格中提到。

採用的資料結構

先來提出幾個問題

1.為什麼要用棧來實現?有什麼好的地方?
2.di 有什麼作用?為什麼要它?
3.棧空與棧不空,有什麼用?
4.大體思路是什麼?
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#define  MAXSTACKSIZE  100  //棧的大小
#define      N          10     //二維迷宮大小
#define  Entrance_row   0  //入口
#define  Entrance_col   1 
#define  Exit_row   8   //出口
#define  Exit_col   9
typedef struct position{   //座標   
    int x;
    int y;
}position ;
typedef struct SElement {
    position p;   
    int di;     //記錄已經走了多少個方向
}SElement ; 
typedef struct Mystack{
    SElement  *top;
    SElement  *base;
    int stackSize ;
}Mystack ;

int Maze[N][N]={
    {2,0,2,2,2,2,2,2,2,2},//1
    {2,0,0,2,0,0,0,2,0,2},//2
    {2,0,0,2,0,0,0,2,2,2},//3
    {2,0,0,0,0,2,2,0,0,2},//4
    {2,0,2,2,2,0,2,0,2,2},//5
    {2,0,0,0,2,0,0,0,0,2},//6
    {2,0,2,0,0,0,2,0,0,2},//7
    {2,0,2,2,2,0,2,2,0,2},//8
    {2,2,0,0,0,0,0,0,0,0},//9
    {2,2,2,2,2,2,2,2,2,2} //10
};

int IsEmptyStack(Mystack *path);

int InitStack(Mystack *path)   // top ,base  ,size 
{
    path->top = path->base =(SElement *)malloc(sizeof(SElement)*MAXSTACKSIZE);
    if(path->top == NULL )
    {
        printf("Init  stack is failed !!! \n");
        return -1;
    }
    path->stackSize = MAXSTACKSIZE;
    return 0;
}

int pop(Mystack *path ,SElement *t)  //從path 中出一個元素給t 
{
    if(IsEmptyStack(path) == 1)
        return 0;
    *t = *(path->top-1);
    path->top-- ;
    return 1;
}

int push(Mystack *path ,SElement p) //入棧
{
    *(path->top) = p ;
    path->top++;
}

int IsEmptyStack(Mystack *path)
{
    if(path->top == path->base )   return 1;  //空棧返回 1  
    else return 0 ;
}
int print_MAZE(int Maze[N][N])  //列印迷宮
{
    int i,j;
    for(i= 0 ;i< N;i++)
    {
        for(j= 0 ;j< N ;j++)
        {
            if(Maze[i][j] == 10)    printf("\033[31m  *  \033[0m") ;
            else  printf("  %d  ",Maze[i][j]);
        }
        printf("\n\n");
    }
}
int check(position now_try) //檢查下一步是否越界和是否是牆 
{

    if(Maze[now_try.x][now_try.y]  !=  0)  //0  代表走的通
        return 0;
    if(now_try.x <  0 && now_try.x >=  N  )
        return 0;
    if(now_try.y <  0 && now_try.y >=  N  )
        return 0;
    return 1;
}

position   NextPosition(position  now_try ,int direction)  //獲得下一個位置的座標 x,y
{
    position next ;
    next.x= now_try.x;
    next.y  = now_try.y;
    switch(direction)
    {
        case 4:next.y+=1;break; //東
        case 3:next.x+=1;break; //南
        case 1:next.x-=1;break;//西
        case 2:next.y-=1;break;//北
    }
    return next ;
}
int main(void)
{
    print_MAZE(Maze) ;
    Mystack  path ;
    InitStack(&path);
    position  now_try ; //所嘗試的位置
    now_try.x= Entrance_row;
    now_try.y= Entrance_col;
    do{
        if(check(now_try)) //進入if 語句就說明這個點能走,就把他賦值為10 ,入棧,找下一步,繼續
        {
            Maze[now_try.x][now_try.y]  =10 ;
            SElement temp ;
            temp.p.x= now_try.x;
            temp.p.y= now_try.y;
            push(&path,temp);

            if(now_try.x == Exit_row && now_try.y == Exit_col )
                break;
            now_try  = NextPosition(now_try,1);  //先向一個方向進行探索
            printf("\033c"); // 動態演示所走的路的語句
            print_MAZE(Maze);
            usleep(800000);
        }
        else     //這個點為 2 ,不能走,那麼就取出它的上一個(即棧頂元素),尋找其他方向
        {
            if(IsEmptyStack(&path) !=  1)  //棧不空
            {
                SElement t ;
                pop(&path,&t);    //要在被調函式中改變t
                while(t.di == 4 && IsEmptyStack(&path) !=  1){   //檢查是否四個方向都已經被走過
                    Maze[t.p.x][t.p.y] = 9 ;   //9 代表已經被探索過的路
                    pop(&path,&t);
                }
                if(t.di < 4) //如果四個方向沒有走夠,就換一個方向走
                {
                    now_try = NextPosition(t.p,t.di+1);
                    t.di++;
                    push(&path,t);
                }
            }
        }
    }while( IsEmptyStack(&path) ==  0  );  //0 就是有元素
    printf("\033c");
    print_MAZE(Maze);
    return 0;
}

執行截圖:

這裡寫圖片描述

問題解答:

1.首先我們都知道棧有先進後出的特點,那麼我們的迷宮是否需要這種特點吶。如果走的通,那麼就走,如果走不通,那是不是要回到前一步,找另外一個方向走。那麼前一步怎麼儲存?是不是符合一個先存後取的順序!OK !這不正好與我們的棧的特點重合嗎。

2.di 的作用有兩個。一是表示方向,二是表示走了幾個方向了。是不是感覺很拗口。那麼我來簡單解釋一下。用1,2,3,4來表示東南西北,如果di==3,那麼就說明北面還沒有走,如果di == 4,那麼就說明四個方向都已經走過了。

3.棧空與棧不空,有什麼用?假如我們將迷宮改成了這樣,那麼會發生什麼?

int Maze[N][N]={
    {2,0,2,2,2,2,2,2,2,2},
    {2,0,2,2,2,2,2,2,2,2},
    {2,0,2,2,2,2,2,2,2,2},
    {2,0,2,2,2,2,2,2,2,2},
    {2,0,2,2,2,2,2,2,2,2},
    {2,0,2,2,2,,2,2,2,2},
    {2,0,2,2,2,2,2,2,2,2},
    {2,0,2,2,2,2,2,2,2,2},
    {2,2,2,2,2,2,2,2,2,2},
    {2,2,2,2,2,2,2,2,2,2} 
};

是不是會依次入棧,然後依次出棧,出棧之後是不是會棧空,如果不判斷棧空的話是不是會陷入一種死迴圈的狀態吶。

4.核心程式碼:

    now_try.x= Entrance_row;
    now_try.y= Entrance_col;
    do{
        if(check(now_try)) //進入if 語句就說明這個點能走,就把他賦值為10 ,入棧,找下一步,繼續
        {
            Maze[now_try.x][now_try.y]  =10 ;
            SElement temp ;
            temp.p.x= now_try.x;
            temp.p.y= now_try.y;
            push(&path,temp);

            if(now_try.x == Exit_row && now_try.y == Exit_col )
                break;
            now_try  = NextPosition(now_try,1);  //先向一個方向進行探索
            printf("\033c"); // 動態演示所走的路的語句
            print_MAZE(Maze);
            usleep(800000);
        }
        else     //這個點為 2 ,不能走,那麼就取出它的上一個(即棧頂元素),尋找其他方向
        {
            if(IsEmptyStack(&path) !=  1)  //棧不空
            {
                SElement t ;
                pop(&path,&t);    //要在被調函式中改變t
                while(t.di == 4 && IsEmptyStack(&path) !=  1){   //檢查是否四個方向都已經被走過
                    Maze[t.p.x][t.p.y] = 9 ;   //9 代表已經被探索過的路
                    pop(&path,&t);
                }
                if(t.di < 4) //如果四個方向沒有走夠,就換一個方向走
                {
                    now_try = NextPosition(t.p,t.di+1);
                    t.di++;
                    push(&path,t);
                }
            }
        }
    }while( IsEmptyStack(&path) ==  0  );  //0 就是有元素

大體思路:

這裡寫圖片描述

參考資料:參考資料