佇列,廣度搜尋-ZOJ 1148 The Game (BFS)

許佳佳233發表於2016-06-17

大致題意:

有一個BOARD上有很多很多相同大小的卡片。有的位置是空的,有的位置放了卡片。問卡片a和卡片b能不能用橫縱直線段連起來,如果能求連線最少由多少段組成,連線可以暫時離開BOARD。

理解

1、題目是想要得到連線最少由多少段組成,我們可以理解為連線的轉彎個數+1。此處千萬不要與路徑最短等同!
2、此題有點像尋找類似“連連看”的路徑,但是並不等同,連連看只允許轉2個彎,也就是隻能有3段。但是此處是無論轉多少次都可以。

思路

1、資料結構採用二維陣列

由於連線可以暫時離開BOARD,表示BOARD之外也可以走。直接給二維陣列的行和列各自+2就行了,最上面、下面、左面、右面都放空格‘ ’,表示可以走。也就是說,實際我們使用的陣列的大小為(m+2)*(n+2)

2、關於路徑的搜尋,我們採用廣度搜尋

也就是從初始點開始(上下左右四個方向搜尋),我們先搜尋完只有1個線段的,然後再搜尋只有2個線段的,再搜尋只有3個線段的,以此類推。這樣就能夠保證,一旦搜尋到了,返回的資料一定是線段數最少的。

搜尋1個線段的時候比較容易思考,就是隻要遍歷完初始點的四個方向。
那麼搜尋2個線段如何實現呢?
當我們在搜尋1個線段的時候,我們把搜尋的每一個點都存起來,然後我們按照順序對其中的點執行一樣的操作——搜尋那個點的上下左右四個方向(只有一個線段的情況)。這樣,相對於初始點而言,就是隻有2個線段的情況了。

3、那麼如何儲存這些點呢?很容易就可以想到,用佇列

4、題目中還有容易讓人想到的問題就是,如何保證不進入死迴圈,讓判斷效率更高。

這點其實在真正實現的時候,是自然而然就解決的問題。

我們可以再使用一個二維陣列來儲存每個點的走過的情況(在筆者demo中直接是存放該點屬於第幾層的線段),如果一個點已經走過了,那麼在判斷的時候就直接過濾。

讀者可能疑問,直接過濾不會導致漏掉一些路徑嗎
讓我們再次回顧一下我們遍歷的過程,我們是先判斷只有1個線段的路徑是否可行,再判斷2個線段,3個線段,以此類推。
假設,讓我在判斷2個線段中的時候,發現有一個點已經走過了,那麼必然是1個線段的時候,就已經放入佇列了。那麼,倘若我再次放入佇列,我最終所輸出的線段個數,必然1個線段的時候放入佇列的那個值大1,那麼也就沒有必要進行判斷了。

程式碼:

#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;

//node用來記錄遍歷的x和y,放入佇列用
struct node
{
    int x,y;
};

//fx與fy方便朝四個方向遍歷
int fx[]= {-1,1,0,0};
int fy[]= {0,0,-1,1};

char a[80][80];    //a用來存放使用者輸入的X和空格
int step[80][80];  //step是重點,表示記錄的該點屬於第幾段(轉彎個數+1),為-1的時候表示沒有走過。
int n,m;    //n為行,m為列

int bfs(int sx,int sy,int ex,int ey)
{
    //建立佇列,並把第一個點的位置放入佇列
    queue<node> q;
    node s;
    s.x = sx;
    s.y = sy;
    q.push(s);

    step[sx][sy] = 0;
    a[sx][sy] = ' ';
    a[ex][ey] = ' ';

    //cur表示佇列中當前位置,t主要用來將新的node放入佇列
    node Front,cur,t;
    //只要佇列不為空,就會一直遍歷
    //有兩種可能,一是到達了終點,跳出while。另一種是佇列遍歷結束,無法到達終點。
    while(!q.empty())
    {
        Front = q.front();//從第一個點開始檢視,先記錄第一個點的位置

        if(Front.x == ex && Front.y == ey)
            return step[ex][ey];

        //檢視這個點的四個方向
        for(int i=0; i<4; i++)
        {
            cur = Front;
            t.x = cur.x+fx[i];
            t.y = cur.y+fy[i];

            //這個while表示會朝同一個方向一致檢視,直到這個方向不能走了
            //x與y不能越界,該位置上不能有卡片,並且該位置沒有走過
            while(t.x>=0 && t.x<=n+1 && t.y>=0 && t.y<=m+1 && a[t.x][t.y]==' ' && step[t.x][t.y]==-1)
            {
                //在Front的步數基礎上+1,,表示這個
                step[t.x][t.y] = step[Front.x][Front.y]+1;
                q.push(t);

                cur = t;
                t.x = cur.x+fx[i];
                t.y = cur.y+fy[i];
            }
        }
        //由於這個點已經遍歷完了,就把它彈出佇列
        q.pop();
    }
    return -1;
}

int main()
{
    int count1=1;
    while(scanf("%d%d",&m,&n)!=EOF)  //n行m列
    {
        if(m==0 && n==0)
            break;
        else
            printf("Board #%d:\n",count1++);;
        getchar();

        //用來放卡片的位置實際是1~m列和1~n行。
        //0與m列,0與n行由於在題目中都時可用走的,所以都用來放空格‘ ’。
        for(int i=1; i<=n; i++)
            gets(a[i]+1);
        for(int i=0; i<=m+1; i++)
        {
            a[0][i]=' ';
            a[n+1][i]=' ';
            a[i][0]=' ';
            a[i][m+1]=' ';
        }


        int count2=1;
        int sx,sy,ex,ey;//sx,sy為起始點位置,ex,ey為終點位置
        while(scanf("%d%d%d%d",&sy,&sx,&ey,&ex)!=EOF)
        {
            if(sx==0 && sy==0 && ex==0 && ey==0)
                break;
            else
                printf("Pair %d: ",count2++);

            //step表示記錄的該點的轉彎的個數,先設定為-1表示沒有走過。
            memset(step,-1,sizeof(step));

            int ans = bfs(sx,sy,ex,ey);

            if(ans == -1)
                printf("impossible.\n");
            else
                printf("%d segments.\n",ans);

            //如果之前已經走通,那麼起始點與
            a[sx][sy] = a[ex][ey] ='X';
        }
        printf("\n");
    }
    return 0;
}

相關文章