可能是目前爲止最爲詳細的深度優先搜索DFS和廣度優先搜索BFS算法分析

圖的遍歷是指從圖中某一頂點出發,按照某種搜索方法沿着圖中的邊對圖中的所有頂點訪問一次,且僅訪問一次。圖的遍歷常見算法有BFS和DFS。

(一)深度優先搜索DFS

1、基本思路

    DFS 用於找所有解的問題,它的空間效率高,但是找到的不一定是最優解,必須記錄並完成整個搜索,故一般情況下,深搜需要非常高效的剪枝。DFS類似於樹的先序遍歷,搜索策略爲儘可能“深”的搜索一個圖。首先訪問圖中某一起始頂點v,然後由v出發,訪問與v鄰接且未被訪問的任一頂點w1,再訪問與w1鄰接且未被訪問的任意頂點w2,…重複上述過程。當不能再繼續向下訪問時,依次退回到最近被訪問的頂點,若它還有鄰接頂點未被訪問過,則從該點開始繼續上述搜索過程,知道圖中所有頂點均被訪問過爲止。程序僞代碼如下:

bool visited[MAX_VERTEX_NUM];//訪問標記數組
void DFSTraverse(Graph G){
	//對圖G進行深度優先遍歷,訪問函數爲visit()
	for(v=0;v<G.vexnum,++v){//本代碼中是從v=0開始遍歷
		if(!visited[v])
			DFS(G,v);
	}
}
void DFS(Graph G,int v){
	//從頂點v出發,採用遞歸思想,深度優先遍歷圖G
	visit(v);
	visited[v]=true;
	for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
		if(!visited[w]){//w爲u的尚未訪問的鄰接頂點
			DFS(G,w);
		}
}
2、圖示

DFS

3、算法性能分析

   DFS算法是一個遞歸算法,需要藉助一個遞歸工作棧,故它的空間複雜度爲O(|V|)。遍歷圖的過程實質上是對每個頂點查找其鄰接點的過程,其耗費的時間取決於所採用的存儲結構。當以鄰接矩陣表示時,查找每個頂點的鄰接點所需時間爲O(|V|),故總的時間複雜度爲O(|V*V|)。當以鄰接表表示時,查找所有頂點的鄰接點所需時間爲O(|E|),訪問頂點所需時間爲O(|V|),此時,總的時間複雜度爲O(|V|+|E|)。

4、深度優先遍歷的非遞歸寫法

在深度優先搜索的非遞歸算法中使用一個棧S,用來記憶下一步可能訪問的頂點,同時使用了一個訪問標記數組visited[i],在visited[i]中記憶第i個頂點是否在站內或者曾經在棧內。若是,以後他不能再進棧。圖採用鄰接表方式,僞代碼如下所示:

void DFS_Non_Rc(AGraph& G,int v){
	//從頂點v開始進行深度優先搜索,一次遍歷一個連通分量的所有頂點
	int w;//頂點序號
	InitStack(S);//初始化棧S
	for(i=0;i<G.vexnum;i++)
		visited[i]=false;//初始化visited[]
	Push(S,v);
	visited[v]=true;//v入棧,並置visited[v]
	while(!IsEmpty(S)){
		k=Pop(S);//出棧
		visit(k);//先訪問,再將其子結點入棧
		for(w=FirstNeighbor(G,k);w>=0;w=NextNeighbor(G,k,w))
			if(!visited[w]){//未進過棧的頂點進棧
				Push(S,w);
				visited[w]=true;//作標記,以免再次入棧	
		 }//end if
	}//end while
}

(二)廣度優先遍歷BFS

1、基本思想

    BFS 常用於找單一的最短路線,它的特點是 “搜到就是最優解”,類似於二叉樹的層序遍歷算法,它的基本思想是:首先訪問起始頂點v,接着由v出發,依次訪問v的各個未訪問過的鄰接頂點w1,w2,w3,…wi,然後再依次訪問w1,w2,…,wi的所有未被訪問過的鄰接頂點…依次類推,直到圖中所有頂點都被訪問過爲止。類似的思想還將應用於Dijkstra單源最短路徑算法和Prim最小生成樹算法。其實現藉助於一個輔助隊列。僞代碼如下所示:

bool visited[MAX_BERTEX_NUM];//訪問標記數組
void BFSTraverse(Graph G){
	//對圖G進行廣度優先遍歷,設訪問函數爲visit()
	for(i=0;i<G.vexnum,++i)
		visited[i]=FALSE;
	InitQueue(Q);
	for(i=0;i<G.vexnum;++i)//從0號頂點開始遍歷
		if(!visited[i])
			BFS(G,i);//vi未訪問過,從vi開始BFS
}
void BFS(Graph G,int v){
	//從頂點v出發,廣度優先遍歷圖G,算法藉助一個輔助隊列Q
	visit(v);//訪問初始頂點v
	visited[v]=true;
	Enqueue(Q,v);//頂點v入隊列
	while(!isEmpty(Q)){
		DeQueue(Q,v);//頂點v出隊列
		for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
		//檢測v所有鄰接點
		if(!visited[w]){//w爲v的尚未訪問的鄰接頂點
			visit(w);//訪問頂點w
			visited[w]=true;//對w做已訪問標記
			EnQueue(Q,w);//頂點w入隊列
		}
	}
}
2、圖示

BFS

3、算法性能分析

   無論是使用鄰接表還是鄰接矩陣的存儲方式,BFS算法都需要藉助一個輔助隊列Q,n個頂點均需入隊一次,在最壞的情況下,空間複雜度爲O(|V|)。
   當採用鄰接表存儲方式時,每個頂點均需搜索一次(或入隊一次),故時間複雜度爲O(|V|),在搜索任一頂點的鄰接點時,每條邊至少訪問一次,故時間複雜度爲O(|E|),算法總時間複雜度爲O(|V|+|E|)。當採用鄰接矩陣存儲方式時,查找每個頂點的鄰接表所需時間爲O(|V|),故算法總時間複雜度爲O(|V|^2)。


4、應用—BFS算法求解非帶權圖單源最短路徑問題
void BFS_MIN_Distance(Graph G,int u){
	//d[i]表示從u到i結點的最短路徑
	for(i=0;i<G.vexunm;i++)
		d[i]=;//初始化路徑長度
		visited[u]=true;
		d[u]=0;
		EnQueue(Q,u);
		while(!isEmpty(Q)){
		DeQueue(Q,u);//隊頭元素u出隊
		for(w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w))
			if(!visited[w]){
			//w爲u尚未訪問的鄰接頂點
			visited[w]=true;
			d[w]=d[u]+1;//路徑長度+1
			EnQueue(Q,w);//頂點w入隊
		}//if
	}//while
}

(三)經典算法題目分析

1.Red and Black

There is a rectangular room, covered with square tiles. Each tile is colored either red or black. A man is standing on a black tile. From a tile, he can move to one of four adjacent tiles. But he can’t move on red tiles, he can move only on black tiles.
Write a program to count the number of black tiles which he can reach by repeating the moves described above.

  • Input:

The input consists of multiple data sets. A data set starts with a line containing two positive integers W and H; W and H are the numbers of tiles in the x- and y- directions, respectively. W and H are not more than 20.
There are H more lines in the data set, each of which includes W characters. Each character represents the color of a tile as follows.
‘.’ - a black tile
‘#’ - a red tile
‘@’ - a man on a black tile(appears exactly once in a data set)
The end of the input is indicated by a line consisting of two zeros.

  • Output:

For each data set, your program should output a line which contains the number of tiles he can reach from the initial tile (including itself).

  • SampleInput:

6 9
…#.
…#





#@…#
.#…#.
11 9
.#…
.#.#######.
.#.#…#.
.#.#.###.#.
.#.#…@#.#.
.#.#####.#.
.#…#.
.#########.

11 6
…#…#…#…
…#…#…#…
…#…#…###
…#…#…#@.
…#…#…#…
…#…#…#…
7 7
…#.#…
…#.#…
###.###
…@…
###.###
…#.#…
…#.#…
0 0

  • Sample Output

45
59
6
13

題目大意:題意:給你一張圖,圖上有黑色,紅色兩種方塊,人只能走黑色塊,問你
人最多能走多少個黑色塊
輸入:
w、h代表圖的寬度高度,再給出圖
@代表人的位置,#代表紅色,.代表黑色

使用兩種搜索方式進行搜索,如圖所示
該題主要是用bfs/dfs求最優路徑,屬於暴力搜索,上圖是分別使用dfs和bfs算法走的路徑示意圖,下面是兩種方法代碼

/**
		使用DFS算法進行搜索最優
*/
#include <iostream>
#include<cstring>
#include<algorithm> 
using namespace std;
int vis[25][25];//標記是否走過 
char map[25][25];//走的圖 
int to[4][2]={0,1,0,-1,1,0,-1,0};//相鄰位置
int ans;//記錄結果
int w,h;
void dfs(int x,int y){
	if(vis[x][y]) return ;//已經訪問過,則不訪問 
	if(map[x][y]=='#') return;//若爲#則不訪問
	vis[x][y]=1;//標記已訪問
	ans++;//步數+1
	for(int i=0;i<4;i++){
		int xx=x+to[i][0];
		int yy=y+to[i][1];
		if(!vis[xx][yy]&&map[xx][yy]!='#'){//未訪問或者不爲'#'
		//在範圍內 
			if(xx>=1&&xx<=h&&yy>=1&&yy<=w){
				dfs(xx,yy);
			}
		}
	} 
	
} 
int main(int argc, char** argv) {
	int x,y;
	while(~scanf("%d%d",&w,&h)&&w+h){
		ans=0;
		memset(vis,0,sizeof(vis));
		for(int i=1;i<=h;i++){
			scanf("%s",map[i]+1);
			for(int j=1;j<=w;j++){
				if(map[i][j]=='@'){
					x=i;
					y=j;
				}
			}
		}
		dfs(x,y);
		printf("%d\n",ans);
	} 
	return 0;
}
/**
		使用BFS算法進行搜索最優
*/
 #include<iostream>
 #include<algorithm>
 #include<cstring>
 #include<queue>
 using namespace std;
 int vis[25][25];
 char map[25][25];
 int to[4][2]={0,1,0,-1,1,0,-1,0};//相鄰位置
 int ans;
 int w,h;
 struct node{
 	int x,y;
 };
 //
 queue<node> q;//需要用到隊列 
 void bfs(int sx,int sy){
 	//若隊列不爲空,則出隊 ,爲了保證隊此時爲空 
 	while(!q.empty()){
 		q.pop();
	 }
	 node a;
	 a.x=sx;
	 a.y=sy;
	 //入隊 
	 q.push(a);
	 //置爲已經訪問過 
	 vis[sx][sy]=1;
	 ans++;//步數++ 
	 while(!q.empty()){
	 	//取隊首元素
		 node cur=q.front();
		 q.pop();//出隊 
		 //在四個方向逐層進行搜索 
		 for(int i=0;i<4;i++){
		 	int xx=cur.x+to[i][0];
		 	int yy=cur.y+to[i][1];
		 	//若超出了範圍,則跳過 
		 	if(xx<1||yy<1||xx>h||yy>w){
		 		continue;
			 }
			 //若已經訪問過 
			if(vis[xx][yy]){
				continue;
			}
			//若爲'#'代表此路不通 
			if(map[xx][yy]=='#'){
			  continue; 
			}
			vis[xx][yy]=1;
			ans++;
			node b;
			b.x=xx;
			b.y=yy;
			q.push(b);
		 } 
	 }
 } 
 int main(){
 	int x,y;
 	while(~scanf("%d%d",&w,&h)&&w+h){
 		ans=0;
 		memset(vis,0,sizeof(vis));
 		for(int i=1;i<=h;i++){
 			//+1跳過'\0' 
 			scanf("%s",map[i]+1);
 			//printf("%s\n",map[i]+1); 
 			for(int j=1;j<=w;j++){
 				//獲得初始位置 
 				if(map[i][j]=='@'){
 					x=i;
 					y=j;
				 }
			 }
		 }
		 bfs(x,y);
		 printf("%d\n",ans);
	 }
 	return 0;
 }
2、最優配餐問題
  • 題目描述

棟棟最近開了一家餐飲連鎖店,提供外賣服務。隨着連鎖店越來越多,怎麼合理的給客戶送餐成爲了一個急需解決的問題。
    棟棟的連鎖店所在的區域可以看成是一個n×n的方格圖(如下圖所示),方格的格點上的位置上可能包含棟棟的分店(綠色標註)或者客戶(藍色標註),有一些格點是不能經過的(紅色標註)。
   方格圖中的線表示可以行走的道路,相鄰兩個格點的距離爲1。棟棟要送餐必須走可以行走的道路,而且不能經過紅色標註的點。
   送餐的主要成本體現在路上所花的時間,每一份餐每走一個單位的距離需要花費1塊錢。每個客戶的需求都可以由棟棟的任意分店配送,每個分店沒有配送總量的限制。
   現在你得到了棟棟的客戶的需求,請問在最優的送餐方式下,送這些餐需要花費多大的成本。

在這裏插入圖片描述

  • 輸入格式

輸入的第一行包含四個整數n, m, k, d,分別表示方格圖的大小、棟棟的分店數量、客戶的數量,以及不能經過的點的數量。
    接下來m行,每行兩個整數xi, yi,表示棟棟的一個分店在方格圖中的橫座標和縱座標。
    接下來k行,每行三個整數xi, yi, ci,分別表示每個客戶在方格圖中的橫座標、縱座標和訂餐的量。(注意,可能有多個客戶在方格圖中的同一個位置)
    接下來d行,每行兩個整數,分別表示每個不能經過的點的橫座標和縱座標。

  • 輸出格式

輸出一個整數,表示最優送餐方式下所需要花費的成本。

  • 樣例輸入

10 2 3 3
1 1
8 8
1 5 1
2 3 3
6 7 2
1 2
2 2
6 8

  • 樣例輸出

29

該題與上一道題的不同之處在於:上一道爲單源求最優,本道題是多源(一至多個分店)求最優,使用BFS實現最佳

#include<iostream> 
#include<cstring>
#include<queue>
using namespace std;
 const int N=1000;
 const int TRUE=1;
 const int DIRECTSIZE=4;
 //定義方向結構體
 struct direct{
 	int drow,dcol;
	  
 }direct[DIRECTSIZE]={{-1,0},{1,0},{0,-1},{0,1}} ;
 int buyer[N+1][N+1];//存儲顧客所在位置
 int visited[N+1][N+1];//標記是否訪問過
 struct node{
 int row,col,step;
 node(){
 
 }
 //自家分店構造函數 
 node(int r,int c,int s){
 	row=r;
 	col=c;
 	step=s;
 } 
}; 
queue<node> q;
int count=0;//訂餐點總數
long long ans=0; 
//多源點進行遍歷 
 void bfs(int n){
 	node front,v;
 	while(!q.empty()){
 		//首先將隊首出隊,從第一家店開始搜索 
 		front=q.front();
 		q.pop();
 		for(int i=0;i<DIRECTSIZE;i++){
 			//移動一格
			 v.row=front.row+direct[i].drow;
			 v.col=front.col+direct[i].dcol;
			 //步數加1
			 v.step=front.step+1;
			 //若行列越界,則跳過
			 if(v.row<1||v.row>n||v.col<1||v.col>n) continue;
			 if(visited[v.row][v.col]) continue;
			 //如果是訂餐點,則計算成本並且累加
			 if(buyer[v.row][v.col]>0){
			 	visited[v.row][v.col]=1;
			 	//點一個餐送一個人,有可能這些顧客在一個點 
			 	ans+=buyer[v.row][v.col]*v.step;
			 	//若已經遍歷完所有買家,則return 
			 	if(--count==0){
			 		return ;
				 }
			 }
			 //向前繼續搜索
			 visited[v.row][v.col]=1;
			 q.push(v);//將v加入隊尾,表示已經訪問過 
		 } 
    }
 	
 }
 /**
    	先將所有的餐廳信息(座標以及步數)入隊,
	 在遍歷一個店鋪之後就會將擴展的上右下左四個方向入隊,
	 直到最後一個餐廳結束,就完成了所有店鋪的擴展。
	以此類推,將每一個點都要遍歷一下。每到達客戶的地點,就會計算相應的費用。
 */ 
 int main(){
 	int m,k,d,x,y,c;
 	memset(buyer,0,sizeof(buyer));
 	memset(visited,0,sizeof(visited));
 	//輸入數據
	 cin>>n>>m>>k>>d;
	 for(int i=1;i<=m;i++){
	 	cin>>x>>y;
	 	//將各個分店加入隊列中
		 q.push(node(x,y,0)) ;
		 visited[x][y]=true;
	 }
	 for(int i=0;i<k;i++){
	 	cin>>x>>y;
	 	cin>>c;
	 	//統計客戶所在地點數量(多個客戶可能在同一地點) 
	 	if(buyer[x][y]==0){
	 		count++;//客戶所在地點數量 
		 }
		 buyer[x][y]+=c;//統計某個地點的訂單數量 
	 }
	 //將不能經過的座標置爲true 
	 for(int i=0;i<d;i++){
	 	cin>>x>>y;
	 	visited[x][y]=true;
	 } 
	 //廣度優先搜索
	 bfs(n);
	 cout<<ans<<endl; 
 	return 0;
 } 
3、CCF201604-4 遊戲
  • 問題描述

 小明在玩一個電腦遊戲,遊戲在一個n×m的方格圖上進行,小明控制的角色開始的時候站在第一行第一列,目標是前往第n行第m列。
 方格圖上有一些方格是始終安全的,有一些在一段時間是危險的,如果小明控制的角色到達一個方格的時候方格是危險的,則小明輸掉了遊戲,如果小明的角色到達了第n行第m列,則小明過關。第一行第一列和第n行第m列永遠都是安全的。
 每個單位時間,小明的角色必須向上下左右四個方向相鄰的方格中的一個移動一格。
 經過很多次嘗試,小明掌握了方格圖的安全和危險的規律:每一個方格出現危險的時間一定是連續的。並且,小明還掌握了每個方格在哪段時間是危險的。
 現在,小明想知道,自己最快經過幾個時間單位可以達到第n行第m列過關。

  • 輸入格式

  輸入的第一行包含三個整數n, m, t,用一個空格分隔,表示方格圖的行數n、列數m,以及方格圖中有危險的方格數量。
  接下來t行,每行4個整數r, c, a, b,表示第r行第c列的方格在第a個時刻到第b個時刻之間是危險的,包括a和b。遊戲開始時的時刻爲0。輸入數據保證r和c不同時爲1,而且當r爲n時c不爲m。一個方格只有一段時間是危險的(或者說不會出現兩行擁有相同的r和c)。

  • 輸出格式

輸出一個整數,表示小明最快經過幾個時間單位可以過關。輸入數據保證小明一定可以過關。

  • 樣例輸入

3 3 3
2 1 1 1
1 3 2 10
2 2 2 10

  • 樣例輸出

6

  • 樣例說明

   第2行第1列時刻1是危險的,因此第一步必須走到第1行第2列。
  第二步可以走到第1行第1列,第三步走到第2行第1列,後面經過第3行第1列、第3行第2列到達第3行第3列。

  • 評測用例規模與約定

  前30%的評測用例滿足:0 < n, m ≤ 10,0 ≤ t < 99。
  所有評測用例滿足:0 < n, m ≤ 100,0 ≤ t < 9999,1 ≤ r ≤ n,1 ≤ c ≤ m,0 ≤ a ≤ b ≤ 100。

  • 問題分析
    本題需要一個三維的標誌來避免重複搜索。除了行列座標外,需要考慮時間問題,故將其設置爲三維數組。因爲一些格在某個時間範圍是危險的,不可進入,但是這個時間範圍之外,是可以隨意進入的。所以有時候需要在一些地方踱步,等過了這段時間再前行,就不能簡單地限制爲進入過的格不能再進入。
#include<iostream> 
#include<cstring>
#include<queue>
using namespace std;
const int N=100;
const int DIRECTSIZE=4;
struct direct{
	int drow,dcol;
	
}direct[DIRECTSIZE]={{-1,0},{1,0},{0,-1},{0,1}};
//需要定義個三維數組,第三維存儲在這個時間是否可以通過
int visited[N+1][N+1][300+1];
struct node{
	int row,col;
	int level;
}; 
int bfs(int n,int m){
	
	node start,front,v;
	//從第一行第一列開始走 
	start.row=1;
	start.col=1;
	start.level=0;
	queue<node> q;
	q.push(start);
	while(!q.empty()){
		front=q.front();
		q.pop();
	    //設置出口 
		//到達終點則結束
		if(front.row==n&&front.col==m) return front.level; 
		for(int i=0;i<DIRECTSIZE;i++){
			//四個方向各向前走一步
			v.row=front.row+direct[i].drow;
			v.col=front.col+direct[i].dcol;
			v.level=front.level+1;
			//行界越界則跳過
			if(v.row<1||v.row>n||v.col<1||v.col>m){
				continue;
			}
			//已經訪問過的點無法再次訪問
			if(visited[v.row][v.col][v.level]) continue;
			//向前搜索:標記v點爲已經訪問過,v點加入隊列中
			visited[v.row][v.col][v.level]=1;
			q.push(v); 
		} 
	}
	return 0;
}
int main(){
	int n,m,t,r,c,a,b;
	memset(visited,0,sizeof(visited));
	cin>>n>>m>>t;
	for(int i=1;i<=t;i++){
		cin>>r>>c>>a>>b;
		//設置方格危險時間,使之那些時間不可進入
		for(int j=a;j<=b;j++){
			visited[r][c][j]=1;
		}
		
	}
	int ans=bfs(n,m);
	cout<<ans<<endl; 
	return 0;
}
 

(四)參考文獻

【1】2018年數據結構考研複習指導. 王道論壇組編.
【2】https://www.cnblogs.com/kungfupanda/p/11248014.html
【3】https://vjudge.net/problem/POJ-1979
【4】參考視頻 https://www.bilibili.com/video/av78091226?from=search&seid=16502200292163627678
【5】https://blog.csdn.net/tigerisland45/article/details/54934916

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章