【演算法】廣度優先搜尋(BFS)I
1. 演算法描述
廣度優先搜尋(breadth first search, BFS)是圖的一種遍歷策略,搜尋過程:先訪問節點v;再依次訪問與v相鄰的節點;訪問這些節點之後,再訪問與之相鄰的節點。也就是說,從廣度上進行搜尋。
DFS與樹的先序遍歷相似,而BFS與數的層序遍歷相似。比如,二叉樹[1],
層序遍歷:ABCDEFG 。若把二叉樹看作圖,BFS的遍歷結果也為ABCDEFG。在層序遍歷過程中,可以注意到先訪問的節點的孩子節點必然先被訪問。根據這個,BFS可以用佇列維護已訪問節點(DFS是用棧)。層序遍歷的佇列實現:
起始點入隊;
while(佇列不為空)
{
佇列長度queue_size;
while(queue_size--)
{
隊頭結點出隊;
若它是所求的目標狀態, 跳出迴圈;
否則, 將隊頭節點的所有子結點全都入隊;
}
}
BFS應用的經典例子[1]:Knight Moves,求馬從起始位置跳到目標位置的最少步數。這個問題跟層序遍歷有點類似,解決方法:按步數BFS遍歷圖中的點,遍歷到目標位置即可求出最少步數。
比如:求從a1跳到e4的最少步數。
第1步所能跳到的點
第2步所能跳到的點
第3步所能跳到的點
則從a1到e4的需要的最少步數是3。
2. Referrence
3. 問題
3.1 POJ 1915
Knight Moves問題。
原始碼:
1915 | Accepted | 560K | 125MS | C++ | 1422B | 2013-11-05 21:33:57 |
#include <iostream> #include <queue> using namespace std; #define MAX 300 struct point { int x,y; }; const int move[8][2]={{-1, -2}, {1, -2}, {-2, -1}, {2, -1}, {-2, 1}, {2, 1}, {-1, 2}, {1, 2}}; int visit[MAX][MAX],steps; queue<point>que; void bfs(point start,point end,int l) { int i,que_size; point head,next; memset(visit,0,sizeof(visit)); //initialization steps=0; while(!que.empty()) //clear the queue que.pop(); visit[start.x][start.y]=1; que.push(start); while(!que.empty()) //lever order traverse { que_size=que.size(); while(que_size--) { head=que.front(); que.pop(); if(head.x==end.x&&head.y==end.y) return; for(i=0;i<8;i++) { next.x=head.x+move[i][0]; next.y=head.y+move[i][1]; if(next.x<0||next.x>=l||next.y<0||next.y>=l) continue; if(!visit[next.x][next.y]) { visit[next.x][next.y]=1; que.push(next); } } } steps++; } } int main() { int test_cases,l; point start,end; scanf("%d",&test_cases); while(test_cases--) { scanf("%d%d%d%d%d",&l,&start.x,&start.y,&end.x,&end.y); bfs(start,end,l); printf("%d\n",steps); } return 0; }
3.2 POJ 3278
追及問題,在n處的FJ可以左移、右移,也可以成兩倍地移,求最快需要多少步追上在k處的cow。
可以構建一個三叉樹,節點n的左孩子為節點n-1,中孩子為節點n+1,右孩子為節點2*n。該問題轉化為層序遍歷:在以節點n為根結點的三叉樹中,求搜尋到節點k所需最少步數,也就是求節點k所在哪一層。
陣列要開到100001,才不會WA。
原始碼:
3278 | Accepted | 868K | 79MS | C++ | 851B | 2013-11-05 09:34:04 |
#include <iostream>
#include <queue>
using namespace std;
#define MAX 100001
int visit[MAX], steps;
queue<int>que;
void bfs(int n,int k)
{
int head,i,next,que_size;
memset(visit,0,sizeof(visit)); //initialization
steps=0;
visit[n]=1; //push n to the queue
que.push(n);
while(!que.empty()) //level order traverse
{
que_size=que.size();
while(que_size--)
{
head=que.front();
que.pop();
if(head==k) return;
for(i=1;i<=3;i++)
{
if(i==1) next=head-1;
else if(i==2) next=head+1;
else next=2*head;
if(next<0||next>MAX) continue;
if(!visit[next])
{
visit[next]=1;
que.push(next);
}
}
}
steps++;
}
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
if(n>=k)
printf("%d\n",n-k);
else
{
bfs(n,k);
printf("%d\n",steps);
}
return 0;
}
3.3 POJ 3126
題目大意:有兩個素數,第一個素數可以每次變換1位,求變換成第二個素數的最少步數。
類Knight Moves問題,用BFS解決。
語句sqrt(int i)發生了過載錯誤,compile error了幾次,可以改成sqrt(double i)。
原始碼:
3126 | Accepted | 268K | 0MS | C++ | 1748B | 2013-12-02 15:36:42 |
#include <iostream>
#include <queue>
#include <cmath>
using namespace std;
#define MAX 10000
int isPrime[MAX],visit[MAX],possible,steps;
queue<int>que;
void getPrime() //列印素數表
{
int i,j;
memset(isPrime,1,sizeof(isPrime));
for(i=1000;i<10000;i++)
{
for(j=2;j<=sqrt((double)i);j++)
if(i%j==0)
{
isPrime[i]=0;
break;
}
}
}
void judge(int next)
{
if(!visit[next]&&isPrime[next])
{
visit[next]=1;
que.push(next);
}
}
void bfs(int start, int end)
{
int i,que_size,head,next;
memset(visit,0,sizeof(visit)); //initialization
possible=0;
steps=0;
while(!que.empty())
que.pop();
visit[start]=1;
que.push(start);
while(!que.empty()) //lever order traverse
{
que_size=que.size();
while(que_size--)
{
head=que.front();
que.pop();
if(head==end)
{
possible=1;
return;
}
for(i=0;i<=9;i++)
{
if(i&&i!=head/1000) //變換千位
{
next=i*1000+head%1000;
judge(next);
}
if(i!=(head%1000)/100) //變換個位
{
next=(head/1000)*1000+i*100+head%100;
judge(next);
}
if(i!=(head%100)/10) //變換十位
{
next=(head/100)*100+i*10+head%10;
judge(next);
}
if(i!=head%10) //變換個位
{
next=(head/10)*10+i;
judge(next);
}
}
}
steps++;
}
}
int main()
{
int test_cases,start,end;
getPrime();
scanf("%d",&test_cases);
while(test_cases--)
{
scanf("%d%d",&start,&end);
bfs(start,end);
if(possible) printf("%d\n",steps);
else printf("Impossible\n");
}
return 0;
}
3.4 POJ 3414
有兩個水壺,容積分別為A、B,有三種操作FILL、DROP、POUR(對2個水壺,共有6種操作),目標狀態為某一個水壺中的水量為C,求最少步數的策略。
可以構建一個六元搜尋樹,用BFS求解。
原始碼:
3414 | Accepted | 328K | 0MS | C++ | 2686B | 2013-12-11 22:17:12 |
#include<iostream>
#include <queue>
using namespace std;
#define MAX 101
struct state
{
int operation;
int potA,potB;
}parent[MAX][MAX];
state start,end;
int A,B,C,possible,steps,visit[MAX][MAX];
void init()
{
scanf("%d%d%d",&A,&B,&C);
start.operation=0; start.potA=0; start.potB=0;
parent[start.potA][start.potB].potA=-1; parent[start.potA][start.potB].potB=-1;
}
void bfs()
{
int que_size,op;
state head,next;
queue<state>que;
possible=0;
steps=0;
memset(visit,0,sizeof(visit));
visit[start.potA][start.potB]=1;
que.push(start);
while(!que.empty())
{
que_size=que.size();
while(que_size--)
{
head=que.front();
que.pop();
if(head.potA==C||head.potB==C)
{
possible=1;
end=head;
return;
}
for(op=1;op<=6;op++)
{
switch(op)
{
case 1: //fill(1)
next.potA=A; next.potB=head.potB;
break;
case 2: //empty(1)
next.potA=0; next.potB=head.potB;
break;
case 3: //POUR(1,2)
if(head.potA>B-head.potB)
{
next.potA=head.potA-(B-head.potB);
next.potB=B;
}
else
{
next.potA=0;
next.potB=head.potA+head.potB;
}
break;
case 4: //fill(2)
next.potA=head.potA; next.potB=B;
break;
case 5: //empty(2)
next.potA=head.potA; next.potB=0;
break;
case 6: //POUR(2,1)
if(head.potB>A-head.potA)
{
next.potA=A;
next.potB=head.potB-(A-head.potA);
}
else
{
next.potA=head.potA+head.potB;
next.potB=0;
}
break;
}
if(next.potA>A||next.potB>B) continue;
if(!visit[next.potA][next.potB])
{
visit[next.potA][next.potB]=1;
que.push(next);
parent[next.potA][next.potB].operation=op;
parent[next.potA][next.potB].potA=head.potA;
parent[next.potA][next.potB].potB=head.potB;
}
}
}
steps++;
}
}
void output(state k) //output the result
{
if(!(k.potA==-1&&k.potB==-1))
{
output(parent[k.potA][k.potB]);
if(k.operation==1) printf("FILL(1)\n");
else if(k.operation==2) printf("DROP(1)\n");
else if(k.operation==3) printf("POUR(1,2)\n");
else if(k.operation==4) printf("FILL(2)\n");
else if(k.operation==5) printf("DROP(2)\n");
else if(k.operation==6) printf("POUR(2,1)\n");
}
}
int main()
{
init();
bfs();
if(possible)
{
printf("%d\n",steps);
output(end);
}
else printf("impossible\n");
return 0;
}
3.5 POJ 1606
POJ 1426的類似版,不同:目標狀態是水壺B的水量為C。
原始碼:
1606 | Accepted | 4180K | 16MS | C++ | 2710B | 2013-12-12 09:43:24 |
#include<iostream>
#include <queue>
using namespace std;
#define MAX 1000
struct state
{
int operation;
int jugA,jugB;
}parent[MAX][MAX];
state start,end;
int A,B,C,visit[MAX][MAX];
void init()
{
start.operation=0; start.jugA=0; start.jugB=0;
parent[start.jugA][start.jugB].jugA=-1; parent[start.jugA][start.jugB].jugB=-1;
}
void bfs()
{
int que_size,op;
state head,next;
queue<state>que;
memset(visit,0,sizeof(visit));
visit[start.jugA][start.jugB]=1;
que.push(start);
while(!que.empty())
{
que_size=que.size();
while(que_size--)
{
head=que.front();
que.pop();
if(head.jugB==C)
{
end=head;
return;
}
for(op=1;op<=6;op++)
{
switch(op)
{
case 1: //fill A
next.jugA=A; next.jugB=head.jugB;
break;
case 2: //empty A
next.jugA=0; next.jugB=head.jugB;
break;
case 3: //pour A B
if(head.jugA>B-head.jugB)
{
next.jugA=head.jugA-(B-head.jugB);
next.jugB=B;
}
else
{
next.jugA=0;
next.jugB=head.jugA+head.jugB;
}
break;
case 4: //fill B
next.jugA=head.jugA; next.jugB=B;
break;
case 5: //empty B
next.jugA=head.jugA; next.jugB=0;
break;
case 6: //pour B A
if(head.jugB>A-head.jugA)
{
next.jugA=A;
next.jugB=head.jugB-(A-head.jugA);
}
else
{
next.jugA=head.jugA+head.jugB;
next.jugB=0;
}
break;
}
if(next.jugA>A||next.jugB>B) continue;
if(!visit[next.jugA][next.jugB])
{
visit[next.jugA][next.jugB]=1;
que.push(next);
parent[next.jugA][next.jugB].operation=op;
parent[next.jugA][next.jugB].jugA=head.jugA;
parent[next.jugA][next.jugB].jugB=head.jugB;
}
}
}
}
}
void output(state k) //output the result
{
if(!(k.jugA==-1&&k.jugB==-1))
{
output(parent[k.jugA][k.jugB]);
if(k.operation==1) printf("fill A\n");
else if(k.operation==2) printf("empty A\n");
else if(k.operation==3) printf("pour A B\n");
else if(k.operation==4) printf("fill B\n");
else if(k.operation==5) printf("empty B\n");
else if(k.operation==6) printf("pour B A\n");
}
}
int main()
{
//freopen("in.txt","r",stdin);
while(~scanf("%d%d%d",&A,&B,&C))
{
init();
bfs();
output(end);
printf("success\n");
}
return 0;
}