廣度優先搜索複習筆記

前言

BFS 全稱是 \(Breadth First Search\),中文名是寬度優先搜索,也叫廣度優先搜索。node

是圖上最基礎、最重要的搜索算法之一。ios

所謂寬度優先。就是每次都嘗試訪問同一層的節點。 若是同一層都訪問完了,再訪問下一層。算法

這樣作的結果是,BFS 算法找到的路徑是從起點開始的 最短 合法路徑。換言之,這條路所包含的邊數最小。數組

在 BFS 結束時,每一個節點都是經過從起點到該點的最短路徑訪問的。框架

算法過程能夠看作是圖上火苗傳播的過程:最開始只有起點着火了,在每一時刻,有火的節點都向它相鄰的全部節點傳播火苗。函數

固然也能夠應用於其餘的一些結構中進行搜索spa

框架

void BFS()
{
	memset(vis,0,sizeof(vis));
	queue<數據類型> q;
	q.push(初始值);
	vis[初始值]=1;
	while(!q.empty())
	{
		int k=q.front();
		q.pop();
		//....操做,處理 
		if(符合條件)
		{
			q.push(當前節點);
			vis[當前節點]=1; 
		} 
	}
}

例題講解

P2730 [USACO3.2]魔板 Magic Squares

思路

根據題目要求先用函數處理好每個狀態,運用\(set\)判重,由於每一層循環的操做都會是全部的操做數\(+1\),每一次彈出時判斷一下是否是最終狀態便可code

代碼
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
using namespace std;
const int N=10;
struct node{
	string xu;
	int num;
};
string mb;
string yuan="a12345678";
queue<string> q;
map<string,string> vis;//判斷函數,判斷這一種狀況有沒有走過 
string A(string x) 
{
	string pd=x;
	swap(x[1],x[8]);
	swap(x[2],x[7]);
	swap(x[3],x[6]);
	swap(x[4],x[5]);
	if(!vis.count(x))//沒有這種狀況沒有走過,就放進去繼續搜索 
	{
		vis[x]=vis[pd]+'A';
		q.push(x);
	}
	return x;
}
string B(string x)
{
	string pd=x;
	char up=x[4];
	char down=x[5];
	x[4]=x[3];
	x[5]=x[6];
	x[3]=x[2];
	x[6]=x[7];
	x[2]=x[1];
	x[7]=x[8];
	x[1]=up;
	x[8]=down;
	if(!vis.count(x))
	{
		vis[x]=vis[pd]+'B';
		q.push(x);
	}
	return x;
}
string C(string x)
{
	string pd=x;
	char three=x[3];
	char six=x[6];
	char seven=x[7];
	char two=x[2];
	x[6]=three;
	x[7]=six;
	x[2]=seven;
	x[3]=two;
	if(!vis.count(x))
	{
		vis[x]=vis[pd]+'C';
		q.push(x);
	}
	return x;
}
void search()
{
	q.push(yuan);
	vis[yuan]="";
	while(!q.empty())
	{
		string k=q.front();
		q.pop();
		A(k);
		B(k);
		C(k);
		if(vis.count(mb)!=0)//當出現了最終狀況的時候
		//這個時候由於每一次隊列的頭跳出能夠看作可行的序列長度+1
		//由於每次走的ABC三種狀況是必定不一樣的,因此若是出現了最終狀況必定是
		//第一次出現的且是操做次數最短的,因此直接輸出就行。 
		{
			cout<<vis[mb].size()<<endl;
			cout<<vis[mb]<<endl;
			return;
		}
	}
}
int main()
{
	mb+="a";
	for(int i=1;i<=8;i++)
	{
		char a;
		cin>>a;
		mb+=a; 
	}//將字符串的位置調到1-8便於操做 
	search(); 
	return 0;
}

LOJ10028.Knight Moves

思路

存一下每一次走的步數,而後直接廣搜就完事了,遇到最終狀況就返回後輸出,注意判重和打標記,由於是屢次詢問,別忘了清空堆裏面存的值和 \(vis\) 的值three

代碼
#include<iostream>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<vector>
using namespace std;
const int N=409;
struct node{
	int x;
	int y;
}; 
queue<node> q;
int dx[8]={-1,-2,-2,-1,1,2,2,1};
int dy[8]={-2,-1,1,2,2,1,-1,-2};
int vis[N][N];
int step[N][N];
int n,L;
int strx,stry,endx,endy;
void search()
{
	q.push((node){strx,stry});
	vis[strx][stry]=1;
	step[strx][stry]=0;
	while(!q.empty())
	{
		node cun=q.front();
		q.pop();
		int x=cun.x;
		int y=cun.y;
		for(int i=0;i<8;i++)
		{
			int kx=x+dx[i];
			int ky=y+dy[i];
			if(kx>=0&&kx<L&&ky>=0&&ky<L&&!vis[kx][ky])
			{//若是沒有找過,判斷是否爲最終狀態 
				if(kx==endx&&ky==endy)
				{
					cout<<step[x][y]+1<<endl;
					return;
				}
				else 
				{
					q.push((node){kx,ky});
					step[kx][ky]=step[x][y]+1;
					vis[kx][ky]=1;
				}
			}
		}
	}
}
int main()
{
	cin>>n;
	while(n--)
	{
		while(!q.empty()) q.pop();//十年OI一場空,隊列不空見祖宗 
		memset(vis,0,sizeof(vis)); 
	    //memset(step,0,sizeof(step));
		cin>>L;
		cin>>strx>>stry;
		cin>>endx>>endy;
		if(strx==endx&&stry==endy)
		{
			cout<<0<<endl;
			continue;
		}
		else search();
	} 
	return 0;
}

P4289 [HAOI2008]移動玩具

思路

對於已經匹配好的點,直接賦值爲 \(0\) 不用再去管他,對於沒有匹配的點,就繼續是原先的位置,再找一個數組存一下沒有匹配的位置,以及與全部能夠進行匹配的點之間的曼哈頓距離,而後用常規的深搜(亂入)比較一下最小值便可隊列

代碼
#include<iostream>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<stack>
#include<map>
#include<vector> 
using namespace std;
struct node {
	int x,y;
} c[26];//未匹配點的座標 
int a[5][5];
int b[5][5];
int pipe[5][5][5][5];
//前兩個下標表示未匹配點座標
//後兩個下標爲待匹配點座標
//存儲未匹配點到待匹配點的最短距離
bool used[5][5];
int sum=0;
int minn=0x7fffffff;
void search(int x,int now) 
{ //深搜找答案。
	if(x>sum) 
	{
		minn=min(minn,now);
		return ;
	}
	for(int i=1; i<=4; i++)
		for(int j=1; j<=4; j++)
			if(pipe[c[x].x][c[x].y][i][j]!=0&&!used[i][j])//枚舉全部沒有匹配的點 
			{
				used[i][j]=1;
				search(x+1,now+pipe[c[x].x][c[x].y][i][j]);
				used[i][j]=0;
			}
}
int main() {
	char pu;
	for(int i=1; i<=4; i++)
		for(int j=1; j<=4; j++) 
		{
			cin>>pu;
			a[i][j]=pu-'0';
		}
	for(int i=1; i<=4; i++)
		for(int j=1; j<=4; j++) 
		{
			cin>>pu;
			b[i][j]=pu-'0';
			if(a[i][j]==b[i][j]) a[i][j]=b[i][j]=0;
		}//若是已經匹配好了,那麼就直接賦值爲0,不用管了 
	for(int i=1; i<=4; i++)
		for(int j=1; j<=4; j++)
			if(a[i][j]==1) 
			{
				c[++sum].x=i;
				c[sum].y=j;
				for(int k=1; k<=4; k++)
					for(int l=1; l<=4; l++)
						if(b[k][l]==1) 
						{
							pipe[i][j][k][l]=abs(i-k)+abs(j-l);
							//記錄一下從不匹配點到另外的不匹配點的最短距離 
						}
			}
	search(1,0);
	printf("%d",minn);
	return 0;
}

P6909 [ICPC2015 WF]Keyboarding

坑點提醒

一、最後須要你手動添加一個\(「*」\)

二、注意在其實位置按鍵的狀況

思路

首先咱們能夠把全部相關涉及到的字符都映射爲數字,方便處理,包括\((A…Z),(1…9),("*""-")\)這些東東,處理的時候別映射 \(0\) 就行

當咱們輸入的時候直接映射到一個二維數組 \(a\) 來裝載鍵盤的每一個鍵

將目標串映射到一個一維數組 \(b\) 中來須要按得每個鍵,最後別忘了手動添加一位來存 \("*"\)

那麼如何選擇位置呢,若是在搜索的過程當中暴力美劇會很是耗時間,那麼這個時候就能夠預處理每個鍵第一個到達的位置便可

爲了方便咱們要設一個結構體類型的三維數組

結構體的變量分別爲 到達的位置的橫座標 \(x\) ,到達位置的縱座標 \(y\) ,到達這個位置的時候要匹配目標串的哪一位 \(step\) ,以及到這個點已經走了幾步 \(dis\)

三維數組 \(f[k][i][j]\) 表示從 \((i,j)\)\(k\) 方向走到達的第一個位置的信息(即結構體中的內容)

那麼預處理以後就開始廣搜了

在開始廣搜以前,由於咱們是從第一位開始的,那麼咱們最好先預處理一下在第一位一共能夠連續按幾回,而後再把結構體重所表示的數據存進去,在代碼中有就不詳細說了

那麼如今開始廣搜

考慮兩種狀況,咱們要把選擇和匹配分開找

1、

那麼先是匹配,要看一下堆頂的這個位置是否和當前要匹配的位置相同,若是是相同的時候,那就在進行判斷一下這個是否是最終狀態,若是是,那直接把答案賦值爲當前的步數+1就是最有的答案

那若是不是最後的一個狀況呢

能夠設一個 \(vis\) 數組,用來存當前堆頂的點接下來該匹配哪一位了,而後把相應的結構體中的數據都放進去繼續查找

2、

最後再開始選擇

首先枚舉該點四個位置的到達點,固然由於我在預處理位置的時候有一個位置得移動忘加了,因此還要加上,一次移動

首先判斷一下是否越界

而後再判斷一下當前要選擇的地方要比原座標的要處理的位置要更大,那麼就不須要更新 \(vis\) 數組。

不然的話,就更新當前的 \(vis\) 數組爲如今要處理的位置(不用+1,由於是選擇,即跳過當前的匹配直接跳進),而後把當前要選擇的位置的數據放進去

最後直接輸出便可

代碼

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
using namespace std;
const int N=59;
const int M=10009;
struct node{
	int x;
	int y;
	int step;//指下一個到了第幾個位置, 
	int dis;//按了幾回 
}f[5][N][N];//後兩個表示座標,前一個表示方向,即i,j在k方向上到的最近的點 
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
char wor[M];
int n,m,len,a[N][N],b[M],vis[N][N];
map<char,int> mp;
void prepare()//預先將全部的字符串處理成數字類型的,方便處理
{
	for(int i=0;i<=9;i++)
	mp[(char)('0'+i)]=i+1;
	for(int i=0;i<26;i++)
	mp[(char)('A'+i)]=i+11;//1-10被取過了 
	mp['-']=37;
	mp['*']=38; 
} 
void get()//處理一下四個方向能夠到達的點 
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			for(int k=0;k<4;k++)
			{
				int x=i;
				int y=j;
				while(a[x][y]==a[x+dx[k]][y+dy[k]])//若是一致的話 
				{
					x+=dx[k];
					y+=dy[k];//一致加到不一樣爲止 
				}
				f[k][i][j]=(node){x,y,0,0};
			}
}
int search()
{
	memset(vis,0,sizeof(vis));
	queue<node> q;
	int ans=0;
	int k=1;
	while(a[1][1]==b[k]&&k<=len) ++k;//在起點選取的狀況
	q.push((node){1,1,k,k-1});
	vis[1][1]=k;
	while(!q.empty())
	{
		node cun=q.front();
		q.pop();
		int x=cun.x;
		int y=cun.y;
		int now=cun.step; 
		if(a[x][y]==b[now])//此時相等了
		{
			if(now==len) 
			{
				ans=cun.dis+1;
				break;//若是找完了?直接跳出 
			}
			vis[x][y]=now+1;//更新一下,由於按了一遍鍵盤 
			q.push((node){x,y,now+1,cun.dis+1});
		} 
		for(int i=0;i<4;i++)
		{
			node chose=f[i][x][y];
			chose.x+=dx[i];
			chose.y+=dy[i];//預處理上在加一次,保證完整
			if(chose.x<1||chose.x>n||chose.y<1||chose.y>m) continue;//越界了
			if(vis[chose.x][chose.y]>=now) continue;//若是選擇的地方的要處理的位置比如今的要大,不用改,防止變worse 
			vis[chose.x][chose.y]=now;//若是可行,那麼把接下來要弄得位置給存進去
			q.push((node){chose.x,chose.y,now,cun.dis+1});//跳到那一步 
		} 
	} 
	return ans;
}
int main()
{
	prepare();
	while(scanf("%d",&n)!=EOF)//UVA經典輸入方式
	{	
//		cin>>n;
		cin>>m;
		for(int i=1;i<=n;i++)//輸入每一行的字符串
		{
			cin>>wor;		
			for(int j=0;j<m;j++)
				a[i][j+1]=mp[wor[j]];//將每一個字符的表明的數字映射到a數組中
				//從1開始方便 
		} 
		cin>>wor;
		len=strlen(wor);
		for(int i=0;i<len;i++) b[i+1]=mp[wor[i]];//依舊是映射,從1開始
		len++;
		b[len]=38;//注意最後有一個*
		get();
		cout<<search()<<endl; 
	} 
	return 0;
}

P3456 [POI2007]GRZ-Ridges and Valleys

思路

運用廣搜板子去作便可,每一次搜索的時候都把找到的高度相同的點標記一下,避免進入死循環,一開始進入搜索的時候先把山峯山谷的值設爲 \(1\),而後根據後續的判斷,把不匹配的都設爲 \(0\) ,最後加上便可

代碼
//#include<iostream>
//#include<cstdio>
//#include<cstring>
//#include<algorithm>
//#include<cmath>
//#include<map>
//#include<queue>
//#include<stack>
//#include<set>
//#define int long long 
//using namespace std;
//const int N=1009;
//set<int> s;
//int dx[8]={-1,0,1,0,-1,-1,1,1};
//int dy[8]={0,1,0,-1,-1,1,1,-1};
//int n;
//int mount[N][N];
//bool vis[N][N];
//int feng;
//int gu;
//int big(int x,int y)//找比他小的 
//{
//	int ret=0; 
//	for(int i=0;i<8;i++)
//	{
//		int kx=x+dx[i];
//		int ky=y+dy[i];
//		if(kx>=1&&kx<=n&&ky>=1&&ky<=n&&mount[x][y]>=mount[kx][ky])
//		//若是大於周圍的,不用判斷周圍的是否是合法,方便判斷 
//		ret++;
//	}
//	return ret;
//}
//int small(int x,int y)//找比他大的 
//{
//	int ret=0; 
//	for(int i=0;i<8;i++)
//	{
//		int kx=x+dx[i];
//		int ky=y+dy[i];
//		if(kx>=1&&kx<=n&&ky>=1&&ky<=n&&mount[x][y]<=mount[kx][ky])
//		//若是小於周圍的,不用判斷周圍的是否是合法,方便判斷 
//		ret++;
//	}
//	return ret;
//} 
//int cha(int x,int y){
//	
//	int ret=0; 
//	for(int i=0;i<8;i++)
//	{
//		int kx=x+dx[i];
//		int ky=y+dy[i];
//		if(kx>=1&&kx<=n&&ky>=1&&ky<=n&&mount[x][y]==mount[kx][ky])
//		//若是小於周圍的,不用判斷周圍的是否是合法,方便判斷 
//		ret++;
//	}
//	return ret;
//
//} 
//bool check_big(int x,int y,int num,int lastx,int lasty)//檢查大的 
//{
//	//cout<<"zsf ak ioi"<<endl; 
//	vis[x][y]=1;
//	int pd=0;//用來判斷符合的有多少
//	int cnt=0;//用來判斷一共須要符合條件的是多少 
//	for(int i=0;i<8;i++)
//	{
//		int kx=x+dx[i];
//		int ky=y+dy[i];
//		if(kx<=0||kx>n||ky<=0||ky>n) continue;
//		if(vis[kx][ky]) continue;//別向回找 
//		if(mount[kx][ky]==num)//若是是相等的值
//		{
//			cnt++;
//			if(small(kx,ky)-cha(kx,ky)==0)//若是周圍沒有比他大的
//			{
//				if(check_big(kx,ky,num,x,y)) pd++;
//				else return false;
//			} 
//			else return false;
//		} 
//	}
//	if(pd==cnt) return true;
//}
//bool check_small(int x,int y,int num,int lastx,int lasty)//檢查小的 
//{
//	//cout<<"zsf ak ioi"<<endl;
//	vis[x][y]=1;
//	int pd=0;//用來判斷符合的有多少
//	int cnt=0;//用來判斷一共須要符合條件的是多少 
//	for(int i=0;i<8;i++)
//	{
//		int kx=x+dx[i];
//		int ky=y+dy[i];
//		if(kx<=0||kx>n||ky<=0||ky>n) continue;
//		if(vis[kx][ky]) continue;//別向回找 
//		if(mount[kx][ky]==num)//若是是相等的值
//		{
//			cnt++;
//			if(big(kx,ky)-cha(kx,ky)==0)//若是周圍沒有比他小的
//			{
//				if(check_small(kx,ky,num,x,y))pd++;
//				else return false;
//			} 
//			else return false;
//		} 
//	}
//	if(pd==cnt) return true;
//}
//void search()
//{
//	for(int i=1;i<=n;i++)
//		for(int j=1;j<=n;j++)
//		{
//			if(vis[i][j]) continue;
//			int jian=cha(i,j);//減掉相等的
//			int lar=big(i,j)-jian;
//			int simp=small(i,j)-jian;
//			//cout<<"i= "<<i<<" j= "<<j<<" 比他大的有"<<simp<<"個"<<" 比他小的有"<<lar<<"個"<<" 和他同樣的有"<<jian<<"個"<<endl; 
//			if(jian==8)//若是周圍都是相同的,那就直接忽略掉,留到之後找
//			continue;
//			else if(lar==0)//若是沒有比他小的 
//			{
//				if(check_small(i,j,mount[i][j],i,j))
//				gu++; 
//			} 
//			else if(simp==0)//若是沒有比他大的
//			{
//				if(check_big(i,j,mount[i][j],i,j))
//				feng++; 
//			} 
//		}
//}
//signed main()
//{
//	cin>>n;
//	for(int i=1;i<=n;i++)
//		for(int j=1;j<=n;j++)
//			scanf("%lld",&mount[i][j]),s.insert(mount[i][j]);
//	if(s.size()==1)
//	{
//		cout<<1<<" "<<1<<endl;
//		return 0; 
//	}
//	search();
//	cout<<feng<<" "<<gu<<endl;
//	return 0;
//} 
/* 暴力深搜,luogu 100pts,LOJ 62pts,好像是爆棧了=_=*/
/* 廣搜大法好!!!*/
#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
#include<cmath>
#include<stack>
using namespace std;
const int N = 1000 + 10;
int dx[8] = {-1, -1, -1, 0, 0, 1, 1, 1};
int dy[8] = {-1, 0, 1, -1, 1, -1, 0, 1};
struct node 
{
    int x, y;
};
//結構體存點
int n, big, small;
int map[N][N], vis[N][N];
bool sg, sf;
queue<node> q;
void search(int x, int y) 
{
    node start;	//初始值
    start.x=x, start.y=y;
    vis[x][y]=1;
    q.push(start);
    while (!q.empty()) 
	{
        node cun=q.front(); 
		q.pop();	//隊首
        for (int i=0;i<=7;i++) 
		{//八方向
            int nx=cun.x+dx[i];
            int ny=cun.y+dy[i];
            if (nx<1||nx>n||ny<1||ny>n) continue;	//越界
            if (map[nx][ny] == map[cun.x][cun.y]&&!vis[nx][ny]) 
			{	// 高度相等打上標記接着搜
                vis[nx][ny] = 1;
                q.push((node){nx, ny});
            }
            else 
			{	// 山峯山谷是否成立
                if (map[nx][ny] > map[cun.x][cun.y]) sf = 0;
                if (map[nx][ny] < map[cun.x][cun.y]) sg = 0;
            }
        }
    }
}

int main() {
    scanf("%d",&n);
    bool flag=0;//判斷一下是否是全部的都相等 
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++) 
		{
            scanf("%d", &map[i][j]);
            if (map[i][j]!=map[1][1]) flag=1;
        }
    if (!flag) 
	{	// 判斷是否所有高度相等
        cout<<1<<" "<<1<<endl;
        return 0;
    }
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++) 
		{
            if (!vis[i][j]) 
			{ //若是是新的聯通塊
                sf=1,sg=1;
                search(i,j);
                big+=sf;
                small+=sg;
            }
        }
    printf("%d %d\n", big, small);
    return 0;
}

結語

當作一個題目的時候,儘可能先有爆搜的思路穩住分數,把暴力分拿滿以後再去思考正解,穩重求進。

相關文章
相關標籤/搜索