BFS(三):雙向廣度優先搜索


      所謂雙向廣度搜索指的是搜索沿兩個方向同時進行:(1)正向搜索:從初始結點向目標結點方向搜索;(2)逆向搜索:從目標結點向初始結點方向搜索;當兩個方向的搜索生成同一子結點時終止此搜索過程。

      廣度雙向搜索通常有兩種方法:(1)兩個方向交替擴展;(2)選擇結點個數較少的那個方向先擴展。方法(2)克服了兩方向結點的生成速度不平衡的狀態,可明顯提高效率。

【例1】Knight Moves (POJ 1915)

Description

Background
Mr Somurolov, fabulous chess-gamer indeed, asserts that no one else but him can move knights from one position to another so fast. Can you beat him?
The Problem
Your task is to write a program to calculate the minimum number of moves needed for a knight to reach one point from another, so that you have the chance to be faster than Somurolov.
For people not familiar with chess, the possible knight moves are shown in Figure 1.

Input

The input begins with the number n of scenarios on a single line by itself.
Next follow n scenarios. Each scenario consists of three lines containing integer numbers. The first line specifies the length l of a side of the chess board (4 <= l <= 300). The entire board has size l * l. The second and third line contain pair of integers {0, ..., l-1}*{0, ..., l-1} specifying the starting and ending position of the knight on the board. The integers are separated by a single blank. You can assume that the positions are valid positions on the chess board of that scenario.
Output

For each scenario of the input you have to calculate the minimal amount of knight moves which are necessary to move from the starting point to the ending point. If starting point and ending point are equal,distance is zero. The distance must be written on a single line.
Sample Input

3
8
0 0
7 0
100
0 0
30 50
10
1 1
1 1
Sample Output

5
28
0

       (1)編程思路1。

       先采用一般的單向廣度優先搜索方法。設置數組step[305][305]記錄走到某個位置需要的步數,數組vis[305][305]來記錄某個位置是否已訪問過(值0代表未訪問,1代表已訪問過)。

       用數組q[]來模擬隊列,front和rear分別為隊頭和隊尾指針。單向廣度優先搜索的框架可寫為:

           隊列初始化,即front=rear=0;

           起點坐標入隊列;

           while (front<rear)      // 隊列不為空

           {

                  隊頭元素出隊,送cur結點;

                  若cur結點的坐標等於終點坐標,則搜索完成,返回;

                  將cur結點按規則展出新結點next(即用循環進行8個方向的新結點生成);

                     若next結點未訪問過,則置相應的值,並將next結點入隊;

            }

      (2)采用單向廣度優先搜索的源程序。

#include <stdio.h>

int vis[305][305], step[305][305];

int dx[] = {-2, -2, -1, 1, 2, 2, 1, -1};

int dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};

struct point

{

     int x, y;

};

int BFS(int start_x,int start_y,int end_x,int end_y,int n)

// 在n*n的棋盤中搜索從起點(start_x,strat_y)到終點(end_x,end_y)所需的最少步數

{

     int front,rear,i;

     point cur,next,q[90005];

     front=rear=0;

     cur.x = start_x;

     cur.y = start_y;

     vis[start_x][start_y] = 1;   // 設置探索標記為1

     q[rear++] = cur;              // 起始坐標入隊

     while (front < rear)

     {

         cur = q[front++];       // 隊頭結點坐標出隊

         for (i=0; i<8; ++i)

         {

             next.x = cur.x + dx[i];

             next.y = cur.y + dy[i];

             if (next.x<0 || next.x>=n || next.y<0 || next.y>=n)

                 continue;

             if (next.x==end_x && next.y==end_y)   // 到達目標位置

                  return step[cur.x][cur.y]+1;

             if (!vis[next.x][next.y])

             {

                 vis[next.x][next.y] = 1;    

                 step[next.x][next.y] = step[cur.x][cur.y] + 1; // 記錄步數

                 q[rear++] = next; // 當前合法坐標位置入隊

             }

        }

    }

    return -1;  // 若搜索不成功,表示不可達

}

int main()

{

    int nCase,sx,sy,tx,ty,size,i,j;

    scanf("%d", &nCase);

    while (nCase--)

    {

         scanf("%d", &size);

         for (i=0;i<size;i++)

                for (j=0;j<size;j++)

                      vis[i][j]=step[i][j]=0;

         scanf("%d %d", &sx, &sy);

         scanf("%d %d", &tx, &ty);

         if (sx==tx && sy==ty)

         {

             printf("0\n");

         }

         else

         {

             printf("%d\n",BFS(sx,sy,tx,ty,size));

         }   

     }

     return 0;

 }

       (3)編程思路2。

      用同一個隊列來保存正向和逆向擴展的結點。開始時,將起點坐標和終點坐標同時入隊列。這樣,第1個出隊的坐標是起點,正向搜索擴展隊列;第2個出隊的坐標是終點,逆向搜索擴展隊列。…,兩個方向的擴展依次交替進行。

       由於采用雙向搜索,如何知道某個結點是正向還是逆向擴展來的呢?

       簡單修改vis[][]數組元素的置值方法即可。初始時,vis數組的全部元素值為0,由正向擴展來的結點的vis對應元素值置為1,由逆向擴展來的結點的vis對應元素值置為2。

      設當前結點為cur,由cur可以擴展出新結點next。若vis[next.x][next.y]==0,則next結點未訪問過,將next結點入隊並進行相應設置;若vis[next.x][next.y]!=0,則next結點已訪問過。由於vis[cur.x][cur.y]記錄的是當前擴展方向(1代表正向,2代表逆向),若vis[next.x][next.y] != vis[cur.x][cur.y],則表示next結點以前按相反的方向訪問過,正向和反向遇到了同一個結點,搜索成功。

      (4)采用雙向廣度優先搜索的源程序。

#include <stdio.h>

int vis[305][305], step[305][305];

int dx[] = {-2, -2, -1, 1, 2, 2, 1, -1};

int dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};

struct point

{

     int x, y;

};

int BFS(int start_x,int start_y,int end_x,int end_y,int n)

// 在n*n的棋盤中搜索從起點(start_x,strat_y)到終點(end_x,end_y)所需的最少步數

{

     int front,rear,i;

      point cur,next,q[90005];

      front=rear=0;

     cur.x = start_x;

     cur.y = start_y;

     vis[start_x][start_y] = 1;  // 從起始位置開始的探索標記為1

     q[rear++] = cur;         // 起始坐標入隊

     next.x = end_x;

     next.y = end_y;

     vis[end_x][end_y] = 2;   // 從終點位置開始的探索標記為 2

     q[rear++] = next;    // 終點坐標入隊

     while (front < rear)

     {

         cur = q[front++];     /* 隊首結點坐標出隊 */

         for (i=0; i<8; ++i)

         {

             next.x = cur.x + dx[i];

             next.y = cur.y + dy[i];

             if (next.x<0 || next.x>=n || next.y<0 || next.y>=n)

                 continue;

             if (!vis[next.x][next.y])

             {

                 vis[next.x][next.y] = vis[cur.x][cur.y];     // 設為與當前探索路徑相同的標記

                 step[next.x][next.y] = step[cur.x][cur.y] + 1; // 記錄步數

                 q[rear++] = next; // 當前合法坐標位置入隊

             }

             else if (vis[cur.x][cur.y] != vis[next.x][next.y])

             {   // 說明從起點出發的探索與從終點出發的探索重合

                 return step[cur.x][cur.y]+step[next.x][next.y]+1;

             }

        }

    }

    return -1;  // 若搜索不成功,表示不可達

}

int main()

{

    int nCase,sx,sy,tx,ty,size,i,j;

     scanf("%d", &nCase);

    while (nCase--)

    {

         scanf("%d", &size);

         for (i=0;i<size;i++)

               for (j=0;j<size;j++)

                    vis[i][j]=step[i][j]=0;

         scanf("%d %d", &sx, &sy);

         scanf("%d %d", &tx, &ty);

         if (sx==tx && sy==ty)

         {

             printf("0\n");

         }

         else

         {

             printf("%d\n",BFS(sx,sy,tx,ty,size));

         }   

     }

     return 0;

 }

(5)編程思路3。

      定義兩個隊列q1[]和q2[]分別用於兩個方向的擴展,兩個隊列的隊頭指針和隊尾指針分別為front1、front2和rear1、rear2。雙向廣度優先搜索的框架還可寫成:
      void BFS()
      {

           將起始節點放入隊列q1,將目的節點放入隊列q2;

           當兩個隊列都未空時,作如下循環
           {
                 如果隊列q1里的未處理節點比q2中的少(即rear1-front1 < rear2-front2),

                        則擴展隊列q1;

                 否則擴展隊列q2;
             }

      }

      (6)采用兩個隊列的雙向廣度優先搜索方法的源程序。

#include <stdio.h>

int vis[305][305], step[305][305];

int dx[] = {-2, -2, -1, 1, 2, 2, 1, -1};

int dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};

struct point

{

     int x, y;

};

int BFS(int start_x,int start_y,int end_x,int end_y,int n)

// 在n*n的棋盤中搜索從起點(start_x,strat_y)到終點(end_x,end_y)所需的最少步數

{

     int front1,rear1,front2,rear2,i,flag;

     point cur,next,q1[45001],q2[45001];

     front1=rear1=0;

     front2=rear2=0;

     cur.x = start_x;

     cur.y = start_y;

     vis[start_x][start_y] = 1;  // 設置正向探索標記為1

     q1[rear1++] = cur;           // 起始坐標入正向隊列

     next.x = end_x;

     next.y = end_y;

     vis[end_x][end_y] = 2;      // 設置逆向探索標記為2

     q2[rear2++] = next;          // 終點坐標入逆向隊列

     while (front1 < rear1 && front2<rear2)

     {

            if (rear1-front1 < rear2-front2)

                    {

                             cur = q1[front1++]; flag=1;    // 擴展正向隊列

                    }

                    else

                    {

                             cur = q2[front2++]; flag=2;    // 擴展逆向隊列

                    }

           for (i=0; i<8; ++i)

          {

             next.x = cur.x + dx[i];

             next.y = cur.y + dy[i];

             if (next.x<0 || next.x>=n || next.y<0 || next.y>=n)

                 continue;

             if (!vis[next.x][next.y])

             {

                 vis[next.x][next.y] = flag;    

                 step[next.x][next.y] = step[cur.x][cur.y] + 1;

                 if (flag==1)

                    q1[rear1++] = next;

                                      else

                    q2[rear2++] = next;

             }

             else if (vis[cur.x][cur.y] != vis[next.x][next.y])

             {

                    return step[cur.x][cur.y]+step[next.x][next.y]+1;

             }

        }

    }

    return -1;  // 若搜索不成功,表示不可達

}

int main()

{

    int nCase,sx,sy,tx,ty,size,i,j;

    scanf("%d", &nCase);

    while (nCase--)

    {

         scanf("%d", &size);

         for (i=0;i<size;i++)

               for (j=0;j<size;j++)

                      vis[i][j]=step[i][j]=0;

         scanf("%d %d", &sx, &sy);

         scanf("%d %d", &tx, &ty);

         if (sx==tx && sy==ty)

         {

             printf("0\n");

         }

         else

         {

             printf("%d\n",BFS(sx,sy,tx,ty,size));

         }   

     }

     return 0;

 }

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM