1. 程式人生 > >【演算法】廣度優先搜尋(BFS)I

【演算法】廣度優先搜尋(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;
}