【算法總結】廣搜


算法總結-廣搜(BFS:breadth-first search

廣度優先搜索算法(用QUEUE)

  1. 把初始節點S0放入Open表(待擴展表)中;
  2. 如果Open表為空,則問題無解,失敗退出;
  3. 把Open表的第一個節點取出放入Closed表,並記該節點為n;
  4. 考察節點n是否為目標節點。若是,則得到問題的解,成功退出;
  5. 若節點n不可拓展,則轉第2步;
  6. 擴展節點n,將其不在Closed表和Open表的子節點(判重)放入Open表的尾部,並為每一個子節點設置指向父節點的指針(或記錄節點的層次),然后轉第2步;

(詳見劉家瑛課程PPT,結合Catch That Cow)

廣搜和深搜的比較

廣搜一般用於狀態表示比較簡單、求最優策略的問題。一層一層的搜,每一條路徑的搜索進度都是一樣的,因此需要用到隊列的知識,不需要使用遞歸.

優點:是一種完備策略,即只要問題有解,它就一定可以找到解 。並且,廣度優先搜索找到的解,還一定是路徑最短的解

缺點:盲目性較大,尤其是當目標節點距初始節點較遠時,將產生許多無用的節點,因此其搜索效率較低。需要保存所有擴展出 的狀態,占用的空間大

深搜幾乎可以用於任何問題,一條路搜到底,不能再往下了,就回溯一步,需要用到遞歸,且只需要保存從起始狀態到當前狀態路徑上的節點

因此dfs適合那些求可行性路徑數目的,而bfs適合求最短路徑之類的(因為一旦搜到,就是最短)

——根據題目要求憑借自己的經驗和對兩個搜索的熟練程度做出選擇 。

數據結構:使用隊列queue完成bfs,通常需要點結構來存儲結點信息

bool vis[5][5];//記錄訪問數組
queue <Node> q;
struct Node {
    int x;
    int y;
};

bfs():廣度遍歷地圖

Node bfs()//返回終點
{
    queue <Node> q;
    Node cur, next;
    cur.x = 0;
    cur.y = 0;//初始結點信息
    q.push(cur);//初始結點入隊,
    while (!q.empty()) //開啟bfs過程
    {
        cur = q.front();//取隊頭結點
        q.pop();
        if (cur.x == 終點.x && cur.y == 終點.y) return cur;//過程結束條件
        int i, nx, ny;//新的待遍歷結點
        for (i = 0; i < 4; i++) {
            nx = cur.x + dx[i];
            ny = cur.y + dy[i];
            if (judge(nx, ny))continue; //不可以走,邊界或者已遍歷等
            next = cur;
            next.x = nx;
            next.y = ny;
            q.push(next);//新結點入隊
        }
    }
}

廣搜例題-poj3278 Catch That Cow

#include<iostream>
#include<cstring>
#include<queue>

using namespace std;

int N, K;//農夫起點和牛的位置
const int MAXN = 100000;//最大坐標范圍
int visited[MAXN + 10];//判重標記,visited[i]=true表示i已經擴展過
struct Step//一個坐標
{
    int x;//位置
    int steps;//達到x所需的步數,即找到牛所需要的時間
    Step(int xx,int s):x(xx),steps(s){}//構造函數
};

queue<Step> q;//隊列,即Open表

int main()
{
    cin >> N >> K;
    memset(visited, 0, sizeof(visited));
    q.push(Step(N, 0));//將農夫起點加入擴展隊列
    visited[N] = 1;//標記N,起點位置已訪問
    while (!q.empty())//Open表如果不為空,則一直擴展,直到Open表為空或者擴展到目標結點
    {
        Step s = q.front();//讀取隊頭元素
        if (s.x == K)//找到目標
        {
            cout << s.steps << endl;
            return 0;//直接結束
        }
        else
        {
            if (s.x - 1 >= 0 && !visited[s.x - 1])//能往左邊走且左邊的結點還沒有被訪問過
            {
                q.push(Step(s.x - 1, s.steps + 1));
                visited[s.x - 1] = 1;
            }
            if (s.x + 1 <= MAXN && !visited[s.x + 1])//能往右邊走且右邊的結點還沒有被訪問過
            {
                q.push(Step(s.x + 1, s.steps + 1));
                visited[s.x + 1] = 1;
            }
            if (s.x * 2 <= MAXN && !visited[s.x * 2])//能往左邊走一倍且左邊的結點還沒有被訪問過
            {
                q.push(Step(s.x * 2, s.steps + 1));
                visited[s.x * 2] = 1;
            }
            q.pop();//隊頭元素出列
        }
    }
    return 0;
}
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;

int vis[100005] = { 0 };
struct Node
{
    int x, t;
};

queue<Node>q;

bool valid(int x)
{
    return (x >= 0 && x <= 100000);
}

int main()
{
    int n, k;
    scanf("%d%d", &n, &k);
    Node s;
    s.x = n;
    s.t = 0;
    vis[n] = 1;
    q.push(s);
    while (!q.empty())
    {
        Node cur = q.front();
        q.pop();
        if (cur.x == k)
        {
            printf("%d", cur.t);
            return 0;
        }
        Node tmp;
        if (valid(cur.x + 1) && !vis[cur.x + 1])
        {
            tmp.x = cur.x + 1;
            vis[cur.x + 1] = 1;
            tmp.t = cur.t + 1;
            q.push(tmp);
        }
        if (valid(cur.x * 2) && !vis[cur.x * 2])
        {
            tmp.x = cur.x * 2;
            tmp.t = cur.t + 1;
            vis[cur.x * 2] = 1;
            q.push(tmp);
        }
        if (valid(cur.x - 1) && !vis[cur.x - 1])
        {
            tmp.x = cur.x - 1;
            vis[cur.x - 1] = 1;
            tmp.t = cur.t + 1;
            q.push(tmp);
        }
    }
    return 0;
}
二刷

廣搜模板(地圖尋路)

struct st{
    ...
};

queue<st> q;

int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};

void bfs(){
    q.push(start_st);
    while(!q.empty()){
        st tmp=q.front();
        q.pop();
        for(int i = 0; i < 4;++i){
            if(...)
                q.push(st(...));
        }
    }
}

尋路例題 POJ 3984 迷宮問題

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <queue>
using namespace std;

bool vis[5][5];
int a[5][5];
int dx[4] = {0,1,0,-1};
int dy[4] = {1,0,-1,0};

struct Node{
    int x;
    int y;
    int s;
    short l[30];
};

bool Noway(int x,int y)
{
    if(x<0 || x>=5 || y<0 || y>=5) return true;
    if(vis[x][y]) return true;
    if(a[x][y]==1) return true;
    return false;
}

Node bfs()
{
    queue <Node> q;
    Node cur,next;
    cur.x = 0;
    cur.y = 0;
    cur.s = 0;
    vis[cur.x][cur.y] = true;
    q.push(cur);
    while(!q.empty()){
        cur = q.front();
        q.pop();
        if(cur.x==4 && cur.y==4)
            return cur;
        int i,nx,ny;
        for(i=0;i<4;i++){
            nx = cur.x + dx[i];
            ny = cur.y + dy[i];
            if(Noway(nx,ny))continue;//不可以走
            vis[nx][ny] = true;
            next = cur;
            next.x = nx;
            next.y = ny;
            next.s = cur.s + 1;
            next.l[cur.s] = i; 
            q.push(next); 
        } 
    }
    return cur;
}


int main()
{
    int i,j;
    for(i=0;i<5;i++){   //讀入迷宮
        for(j=0;j<5;j++){
            scanf("%d",&a[i][j]);
        }
    }
    memset(vis,0,sizeof(vis));
    Node ans = bfs();
    int x,y;
    x = 0,y = 0;
    for(i=0;i<=ans.s;i++){
         printf("(%d, %d)\n",x,y);
         x+=dx[ans.l[i]];
         y+=dy[ans.l[i]];
    }        
    return 0;
}

 

雙向廣搜(DFBS)

定義:從兩個方向以廣度優先的順序同時擴展。

比較:

  • DBFS算法相對於BFS算法來說,由於采用了雙向擴展的方式,搜索樹的寬度得到了明顯的減少,時間復雜度和空間復雜度上都有提高! 
  • 假設1個結點能擴展出n個結點,單向搜索要m層能找到答案,那么擴展出來的節點數目就是: (1-nm)/(1-n) 。
  • 雙向廣搜,同樣是一共擴展m層,假定兩邊各擴展出m/2層,則總結點數目 2 * (1-nm/2)/(1-n)。
  • 每次擴展結點總是選擇結點比較少的那邊進行擴展,並不是機械的兩邊交替。 

 框架:

一、雙向廣搜函數:

void dbfs() {

  1. 將起始節點放入隊列q0,將目標節點放入隊列q1
  2. 當兩個隊列都未空時,作如下循環:如果隊列q0未空,不斷擴展q0直到為空;
    1. 如果隊列q0里的節點比q1中的少,則擴展隊列q0
    2. 否則擴展隊列q1
  3. 如果隊列q0未空,不斷擴展q0直到為空;
  4. 如果隊列q1未空,不斷擴展q1直到為空;

}

二、擴展函數:

int expand(i) //其中i為隊列的編號,0或1 

{           

  • 取隊列qi的頭結點H;           
  • 對H的每一個相鄰節點adj: 

                   1.如果adj已經在隊列qi之中出現過,則拋棄adj; 

                   2.如果adj在隊列qi中未出現過,則: 

                             1) 將adj放入隊列qi;                     

                             2) 如果adj 曾在隊列q1-i中出現過, 則:輸出找到的路徑

}   

需要兩個標志序列,分別記錄節點是否出現在兩個隊列中

 


免責聲明!

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



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