算法總結-廣搜(BFS:breadth-first search)
廣度優先搜索算法(用QUEUE)
- 把初始節點S0放入Open表(待擴展表)中;
- 如果Open表為空,則問題無解,失敗退出;
- 把Open表的第一個節點取出放入Closed表,並記該節點為n;
- 考察節點n是否為目標節點。若是,則得到問題的解,成功退出;
- 若節點n不可拓展,則轉第2步;
- 擴展節點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() {
- 將起始節點放入隊列q0,將目標節點放入隊列q1;
- 當兩個隊列都未空時,作如下循環:如果隊列q0未空,不斷擴展q0直到為空;
- 如果隊列q0里的節點比q1中的少,則擴展隊列q0;
- 否則擴展隊列q1
- 如果隊列q0未空,不斷擴展q0直到為空;
- 如果隊列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中出現過, 則:輸出找到的路徑
}
需要兩個標志序列,分別記錄節點是否出現在兩個隊列中
