廣度優先搜索基礎

1、廣搜的特性(隊列狀態之特性)

當使用BFS遍歷一張無權圖,每次從隊列中取出隊首進行一系列擴展,將擴展成功結點放入隊尾中;
這樣的操做會使得整隊列知足「兩段性」,即對於這張搜索樹中隊列只會保留兩層的結點;ios

證實:算法

  1. 第p層結點擴展時只會擴展第p+1層結點,不會越級擴展p+k層結點(p >= 1&&k >= 2);
  2. 初始的時候隊列中只有一層的結點(或者起始點);
  3. 一+二推得:該隊列只保留了不超過兩層的結點;

因爲是「兩段性」,而從起始點到達節點所走的最短路程爲節點所處的深度,則該隊列知足單調性;
綜上,對於普通BFS,有兩個特性:單調性 、 兩段性;數組

注意:涉及狀態時,必定要明確什麼是第一關鍵字,什麼是第二關鍵字。不然,會出現問題!!測試

2、廣度優先搜索的簡化代碼的技巧

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

如此,在枚舉擴展狀態時能夠預處理幾維數組。spa

3、應用

  • 「走地圖」類:
例題:Bloxorz

網址:http://poj.org/problem?id=3322code

立體推箱子是一個風靡世界的小遊戲。
遊戲地圖是一個N行M列的矩陣,每一個位置多是硬地(用」.」表示)、易碎地面(用」E」表示)、禁地(用」#」表示)、起點(用」X」表示)或終點(用」O」表示)。
你的任務是操做一個1×1×2的長方體。隊列

這個長方體在地面上有兩種放置形式,「立」在地面上(1×1的面接觸地面)或者「躺」在地面上(1×2的面接觸地面)。
在每一步操做中,能夠按上下左右四個鍵之一。
按下按鍵以後,長方體向對應的方向沿着棱滾動90度。
任意時刻,長方體不能有任何部位接觸禁地,而且不能立在易碎地面上。
字符」X」標識長方體的起始位置,地圖上可能有一個」X」或者兩個相鄰的」X」。
地圖上惟一的一個字符」O」標識目標位置。
求把長方體移動到目標位置(即立在」O」上)所須要的最少步數。
在移動過程當中,」X」和」O」標識的位置均可以看做是硬地被利用。遊戲

輸入格式

輸入包含多組測試用例。
對於每一個測試用例,第一行包括兩個整數N和M。
接下來N行用來描述地圖,每行包括M個字符,每一個字符表示一塊地面的具體狀態。
當輸入用例N=0,M=0時,表示輸入終止,且該用例無需考慮。字符串

輸出格式

每一個用例輸出一個整數表示所需的最少步數,若是無解則輸出」Impossible」。
每一個結果佔一行。get

數據範圍

3≤N,M≤500

輸入樣例:
7 7
#######
#..X###
#..##O#
#....E#
#....E#
#.....#
#######
0 0
輸出樣例:

10

此題對於初次接觸廣搜的讀者有些困難。

其實,若是把狀態定義好,並非很難。

定義:(x,y,lie)表示該箱子處於(x,y)的位置;lie表示該箱子的狀態:

  • 若該箱子立在此處,那麼lie=0;
  • 若該箱子向(x,y)右側「躺下了」,那麼lie=1;
  • 若該箱子向(x,y)下方「躺下了」,那麼lie=2;
    這樣,一個狀態定義得十分清晰,其中目標狀態爲(ex,ey,0);
    爲了簡化代碼,咱們使用:
const int next_x[3][4] = {{-2 , 1 , 0 , 0} , {-1 , 1 , 0 , 0} , {-1 , 2 , 0 , 0}};
const int next_y[3][4] = {{0 , 0 , -2 , 1} , {0 , 0 , -1 , 2} , {0 , 0 , -1 , 1}};
const int next_lie[3][4] = {{2 , 2 , 1 , 1} , {1 , 1 , 0 , 0} , {0 , 0 , 2 , 2}};

判斷該箱子滾動狀況,其中next[ lie ] [ i ]即爲當前箱子狀態爲lie,向方向i滾動狀況;

代碼以下:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<cmath>

using namespace std;
const int MAX_size = 500 + 10;
const int dx[4] = {-1 , 1 , 0 , 0} , dy[4] = {0 , 0 , -1 , 1};//ud -> rl

const int next_x[3][4] = {{-2 , 1 , 0 , 0} , {-1 , 1 , 0 , 0} , {-1 , 2 , 0 , 0}};
const int next_y[3][4] = {{0 , 0 , -2 , 1} , {0 , 0 , -1 , 2} , {0 , 0 , -1 , 1}};
const int next_lie[3][4] = {{2 , 2 , 1 , 1} , {1 , 1 , 0 , 0} , {0 , 0 , 2 , 2}};

struct rec
{
	int x , y , lie;
};

rec st , ed;


int n , m , d[MAX_size][MAX_size][3];
queue <rec> Q;
char map[MAX_size][MAX_size];
void init()
{
	bool valid = true;
	int i , j;
	for(i = 1; i <= n; ++ i)
	{
		for(j = 1; j <= m; ++ j)
		{
			if(map[i][j] == 'O') ed.lie = 0 , ed.x = i , ed.y = j;
			else if(map[i][j] == 'X' && valid)
			{
				valid = false;
				st.x = i , st.y = j , st.lie = false;
				if(map[i + 1][j] == 'X') st.lie = 2;
				if(map[i][j + 1] == 'X') st.lie = 1;
			}
		}
	}

	return;
}

bool valid(int x , int y)
{
	if(x < 1 || x > n || y < 1 || y > m) return false;
	return true;
}
bool valid(int x , int y , int lie)
{
	if(map[x][y] == '#') return false;
	if(!valid(x , y)) return false;
	if(lie == 0 && map[x][y] == 'E') return false;
	if(lie == 1 && map[x][y + 1] == '#') return false;
	if(lie == 2 && map[x + 1][y] == '#') return false;


	return true;
}

int bfs()
{
	int x , y , lie;
	while(!Q.empty()) Q.pop();
	Q.push(rec {st.x , st.y , st.lie});

	d[st.x][st.y][st.lie] = 0;
	while(!Q.empty())
	{
		rec now = Q.front();
		Q.pop(); 
                // up down left right
                
		for(int i = 0; i < 4; ++ i)
		{
			x = now.x + next_x[now.lie][i];
			y = now.y + next_y[now.lie][i];
			lie = next_lie[now.lie][i];
			if(d[x][y][lie] != -1) continue;
			if(!valid(x , y , lie)) continue;
			d[x][y][lie] = d[now.x][now.y][now.lie] + 1;
			Q.push(rec {x , y , lie});
			if(x == ed.x && y == ed.y && lie == ed.lie)return d[x][y][lie];
		}
	}
	return -1;
}

int main()
{
	while(scanf("%d %d" , &n , &m) == 2 && n && m)
	{
		memset(d , -1 , sizeof(d));
		memset(map , ' ' , sizeof(map));
		for(int i = 1; i <= n; ++ i) scanf("%s" , (map[i] + 1) );
		init();
		int ans = bfs();
		if(ans != -1) printf("%d\n" , ans);
		else printf("Impossible\n");
	}
	return 0;
}
練習:武士風度的牛

網址:http://noi-test.zzstep.com/contest/0x29「搜索」練習/2906 武士風度的牛

描述

農民John有不少牛,他想交易其中一頭被Don稱爲The Knight的牛。
這頭牛有一個獨一無二的超能力,在農場裏像Knight同樣地跳(就是咱們熟悉的象棋中馬的走法)。
雖然這頭神奇的牛不能跳到樹上和石頭上,可是它能夠在牧場上隨意跳,咱們把牧場用一個x,y的座標圖來表示。

這頭神奇的牛像其它牛同樣喜歡吃草,給你一張地圖,上面標註了The Knight的開始位置,樹、灌木、石頭以及其它障礙的位置,除此以外還有一捆草。
如今你的任務是,肯定The Knight要想吃到草,至少須要跳多少次。

The Knight的位置用’K’來標記,障礙的位置用’*’來標記,草的位置用’H’來標記。
這裏有一個地圖的例子:

11 | . . . . . . . . . .
    10 | . . . . * . . . . . 
     9 | . . . . . . . . . . 
     8 | . . . * . * . . . . 
     7 | . . . . . . . * . . 
     6 | . . * . . * . . . H 
     5 | * . . . . . . . . . 
     4 | . . . * . . . * . . 
     3 | . K . . . . . . . . 
     2 | . . . * . . . . . * 
     1 | . . * . . . . * . . 
     0 ----------------------
                          1 
       0 1 2 3 4 5 6 7 8 9 0

The Knight 能夠按照下圖中的A,B,C,D…這條路徑用5次跳到草的地方(有可能其它路線的長度也是5):

11 | . . . . . . . . . .
  10 | . . . . * . . . . .
   9 | . . . . . . . . . .
   8 | . . . * . * . . . .
   7 | . . . . . . . * . .
   6 | . . * . . * . . . F<
   5 | * . B . . . . . . .
   4 | . . . * C . . * E .
   3 | .>A . . . . D . . .
   2 | . . . * . . . . . *
   1 | . . * . . . . * . .
   0 ----------------------
                         1
     0 1 2 3 4 5 6 7 8 9 0

注意: 數據保證必定有解。

輸入格式

第1行: 兩個數,表示農場的列數C(C<=150)和行數R(R<=150)。
第2..R+1行: 每行一個由C個字符組成的字符串,共同描繪出牧場地圖。

輸出格式

一個整數,表示跳躍的最小次數。

輸入樣例:
10 11
..........
....*.....
..........
...*.*....
.......*..
..*..*...H
*.........
...*...*..
.K........
...*.....*
..*....*..
輸出樣例:

5

此題相對於上一道比較簡單。
代碼 :

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define pa pair <int, int>
using namespace std;
struct rec
{
	int x, y;
} st , ed;

const int MAX_size = 160 + 5;
const int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
const int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};

char map[MAX_size][MAX_size];

int n, m;
int d[MAX_size][MAX_size];

bool book[MAX_size][MAX_size];

void init()
{
	memset(d, 0, sizeof(d));
	memset(book, false, sizeof(book));
	for(int i = 1; i <= n; ++ i)
	{
		for(int j = 1; j <= m; ++ j)
		{
			if(map[i][j] == 'K')
			{
				map[i][j] = '.';
				st.x = i, st.y = j;
				continue;
			}
			if(map[i][j] == 'H')
			{
				map[i][j] = '.';
				ed.x = i, ed.y = j;
				continue;
			}
		}
	}
	return;
}

bool valid(rec next)
{
	if(next.x < 1 || next.x > n || next.y < 1 || next.y > m) return 0;
	if(map[next.x][next.y] == '.' && book[next.x][next.y] == false) return 1;
	return 0;
}
int bfs()
{
	queue <rec> Q;
	while(!Q.empty()) Q.pop();

	book[st.x][st.y] = true;
	Q.push(st);

	while(!Q.empty())
	{
		rec now = Q.front(), next;
		Q.pop();
		for(int i = 0; i < 8; ++ i)
		{
			next.x = now.x + dx[i];
			next.y = now.y + dy[i];
			if(valid(next))
			{
				d[next.x][next.y] = d[now.x][now.y] + 1;
				if(next.x == ed.x && next.y == ed.y) return d[ed.x][ed.y];
				book[next.x][next.y] = true;
				Q.push(next);
			}
		}
	}
	return -1;
}
int main()
{
	scanf("%d%d", &m, &n);
	for(int i = 1; i <= n; ++ i) scanf("%s", map[i] + 1);
	init();
	if(st.x == ed.x && st.y == ed.y) puts("0");
	else printf("%d\n", bfs());
	return 0;
}
  • 邊界填充---flood-fill問題
例題:矩陣距離

網址:http://noi-test.zzstep.com/contest/0x20「搜索」例題/2501 矩陣距離

給定一個N行M列的01矩陣A,A[i][j] 與 A[k][l] 之間的曼哈頓距離定義爲:
dist(A[i][j],A[k][l])=|i−k|+|j−l|

輸出一個N行M列的整數矩陣B,其中:
B[i][j]=min1≤x≤N,1≤y≤M,A[x][y]=1dist(A[i][j],A[x][y])

輸入格式

第一行兩個整數n,m。
接下來一個N行M列的01矩陣,數字之間沒有空格。

輸出格式

一個N行M列的矩陣B,相鄰兩個整數之間用一個空格隔開。

數據範圍

1≤N,M≤1000

輸入樣例:
3 4
0001
0011
0110
輸出樣例:
3 2 1 0
2 1 0 0
1 0 0 1

這就是經典的Flood-fill,就像灑一地水,看能淹了多大的地方。這道題最開始只需將每個位置爲1的座標放進隊列,進行BFS,每次擴展輪數即爲該輪數下位置的最短的B[i][j]值。正確性顯然。

代碼以下:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<bitset>
#include<queue>
#include<cmath>
#define pii pair <int, int>
using namespace std;

queue <pii> Q;
const int MAX_size = 1000 + 5;
const int dx[4] = {-1, 1, 0, 0};
const int dy[4] = {0, 0, -1, 1};
bitset <MAX_size> book[MAX_size];

int n, m, map[MAX_size][MAX_size];
int d[MAX_size][MAX_size];
void bfs()
{
    
	memset(d, 0, sizeof(d));
	while(!Q.empty())
	{
		pii now = Q.front();
		Q.pop();
		int x, y;
		for(int i = 0; i < 4; ++ i)
		{
			x = now.first + dx[i], y = now.second + dy[i];
			if(x < 1 || x > n || y < 1 || y > m) continue;
			if(!book[x][y] && map[x][y] == 0)
			{
				book[x][y] = true;
				d[x][y] = d[now.first][now.second] + 1;
				Q.push(make_pair(x, y));
			}
		}
	}
	return;
}

int main()
{
	while(!Q.empty()) Q.pop();
	scanf("%d %d", &n, &m);
	for(int i = 1; i <= n; ++ i)
	{
		for(int j = 1; j <= m; ++ j)
		{
			scanf("%1d", &map[i][j]);
			if(map[i][j] == 1) Q.push(make_pair(i, j));
		}
		book[i].reset();
	}

	bfs();
	for(int i = 1; i <= n; ++ i)
	{
		for(int j = 1; j <= m; ++ j) printf("%d ", d[i][j]);
		puts("");
	}
	return 0;
}

練習:乳草的入侵
網址:http://noi-test.zzstep.com/contest/0x29「搜索」練習/2907 乳草的入侵

題意

草地像往常同樣,被分割成一個高度爲Y, 寬度爲X的直角網格。
(1,1)是左下角的格(也就是說座標排布跟通常的X,Y座標相同)。
乳草一開始佔領了格(Mx,My)。

每一個星期,乳草傳播到已被乳草佔領的格子四面八方的每個沒有不少石頭的格(包括垂直與水平相鄰的和對角線上相鄰的格)內。

1周以後,這些新佔領的格又能夠把乳草傳播到更多的格里面了。

達達想要在草地被乳草徹底佔領以前儘量的享用全部的牧草。

她很好奇到底乳草要多久才能佔領整個草地。

若是乳草在0時刻處於格(Mx,My),那麼幾個星期之後它們能夠徹底佔領入侵整片草地呢(對給定的數據老是會發生)?
在草地地圖中,」.」表示草,而」*」

表示大石。

好比這個X=4, Y=3的例子。

....
..*.
.**.

若是乳草一開始在左下角(第1排,第1列),那麼草地的地圖將會以以下態勢發展:

....  ....  MMM.  MMMM  MMMM  
      ..*.  MM*.  MM*.  MM*M  MM*M  
      M**.  M**.  M**.  M**.  M**M  
星期數  0     1     2     3     4

乳草會在4星期後佔領整片土地。

輸入格式

第1行: 四個由空格隔開的整數: X, Y, Mx, My
第2到第Y+1行: 每行包含一個由X個字符(」.」表示草地,」*」表示大石)構成的字符串,共同描繪了草地的完整地圖。

輸出格式

輸出一個整數,表示乳草徹底佔領草地所須要的星期數。

數據範圍

1≤X,Y≤100

輸入樣例:
4 3 1 1
....
..*.
.**.
輸出樣例:
41

這道題也同樣,由此觀之,flood-fill實際上就是在一個圖上的廣度優先搜索之擴展,其算法本質跟廣搜差很少。

代碼實現:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<cmath>
#define maxn 100 + 5
using namespace std;
struct rec
{
	int x, y;
};

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

int d[maxn][maxn];
char map[maxn][maxn];
int X, Y, Mx, My, cnt = 0;
bool valid(rec next)
{
	if(next.x < 1 || next.x > Y || next.y < 1 || next.y > X) return false;
	if(d[next.x][next.y] != -1) return false;
	return map[next.x][next.y] == '.';
}
int bfs()
{
	queue <rec> Q;
	while(!Q.empty()) Q.pop();

	d[My][Mx] = 0;
	Q.push(rec {My, Mx});
	int ans = 0;
	while(!Q.empty())
	{
		rec now = Q.front(), next;
		Q.pop();
		for(int i = 0; i < 8; ++ i)
		{
			next.x = now.x + dx[i];
			next.y = now.y + dy[i];
			if(valid(next))
			{
				d[next.x][next.y] = d[now.x][now.y] + 1;
				Q.push(next);
				ans = max(ans, d[next.x][next.y]);
			}
		}
	}
	return ans;
}
int main()
{
	scanf("%d %d %d %d" , &X , &Y , &Mx , &My);
	for(int y = Y; y > 0; -- y)
		scanf("%s", map[y] + 1);
	
	memset(d, -1, sizeof(d));
	printf("%d\n", bfs());
	return 0;
}
相關文章
相關標籤/搜索