1. 程式人生 > >深度優先搜尋(DFS)淺談

深度優先搜尋(DFS)淺談

一,定義

        一個人走進了迷宮,到達甲路口時,他面前有Ñ條路,他選擇了其中的一條,走了一會後到達乙路口,面前又出現Ñ條路,他又選擇了其中的一條,走了一會發現走不通,於是他退回到乙路口又重新選擇了另一條路,發現還是走不通,最終他發現乙路口的任意一條路都是死路,懊惱的他只好退回到一個岔路口在選另一條路......最終他找到了終點。

        通俗說DFS就是有路就走,一條道走到黑,走不通就會上一個分支的再尋路,再不行就會上上個分支點,依次類推,最終找到一條通路。

例1:給定若干個整數A1,A2 .....一個,判斷可否從中選出若干數,使它們的和恰好為ķ。

         思路:從第一個數開始,按順序決定每個數加還是不加,當對這個Ñ個數全部決定完之後判斷它們的和是不是ķ就可以。

                   假設a = {1,2,4,7} k = 13; 圖示:

程式碼表示:

#include<iostream>
using namespace std;

//原始資料 1,2,4,7;目標和13,如果有解則輸出OK 
int a[4] = {1,2,4,7};
int k = 13;
int n = 4;

//假設已選則了前i項,當前的值為sum
bool dfs(int i,int sum) {
	//如果已經選擇完了
	if(i==n) {
		//如果和sum與k相等,返回true
		if(sum==k) {
			return true;
		}
		//否則返回false
		return false;
	}
	//不選擇a[i]的情況
	if(dfs(i+1,sum)) {
		return true;
	}
	//加上a[i]的情況
	if(dfs(i+1,sum+a[i])) {
		return true;
	}
	//即怎麼樣都不能湊成k
	return false;
}

int main() {
	if(dfs(0,0)) {
		cout<<"OK"<<endl;
	} else {
		cout<<"NO"<<endl;
	}
	return 0;

}

          分析:當前這個演算法確實可行,但應該注意,這裡舉出的僅僅是一個規模非常小的問題,即使這樣在最糟的情況下它最多需32步才能得到最終結果;當問題規模稍大時,其時間複雜度O(2 ^ N)極容易超限;同時,在上題中我們無法獲得是哪幾個元素湊成了ķ的解答,為此,我們對上述程式碼進行一點點優化。

優化程式碼:

#include<stdio.h>
int a[4] = {1,2,4,7};             //原始資料 1,2,4,7;目標和13,如果有解則輸出OK
int k = 13;
int n = 4;
int b[4];                         //標記陣列

bool dfs(int x,int sum)  {
	if(sum>k) {               //剪枝,避免不必要的操作
		return false;
	}
	if(x==n) {                //如果前n項計算過了,返回sum=k是否相等
		return sum==k;
	}
	if(dfs(x+1,sum)) {
		b[x]=0;           //如果不加上a[x]的情況,b[x]標記為0
		return true;
	}
	if(dfs(x+1,sum+a[x])) {
		b[x]=1;           //如果加上a[x]的情況,b[x]標記為1
		return true;
	}
	return false;
}

int main() {
	if(dfs(0,0)) {
		printf("YES\n");
		for(int i=0; i<n; i++) {
			if(b[i]) {
				printf("%d ",a[i]);
			}
		}
		printf("\n");
	} else {
		printf("NO\n");
	}
	return 0;
}

執行結果:

上文中提到了剪枝這一概念,顧名思義,在DFS中,有時早已明確的知道無論怎麼選都不會存在解,在這種情況下不再繼續搜尋而是略過,這種方法稱為剪枝。

例2:N * M的矩陣僅由0與1組成,8個連通且值為1的格子算是連線在一起,稱作 “圈0”,求矩陣裡有多少個 “圈0”。

此時"圈0"等於1;

         思路:從任意的位置的0開始,將與它領接的部分用1代替,執行一次DFS後與這個0最初連線的所有0就被替換成了1,直到矩陣中不在有0為止,進行DFS的總次數即是答案。

上程式碼:

#include<iostream>
using namespace std;

int n=3,m=3;
int a[3][6]= {
	{1,1,1,1,1,1},
	{1,0,1,0,0,1},
	{1,1,1,1,1,1}
};

void dfs(int x,int y) {
	//將此處換為1
	a[x][y] = 1;
	//即用(-1,-1),(-1,0),(-1,1),(0,1),(1,1),(1,0),(1,-1),(0,-1),(0,0)表示八個方向
	for(int dx = -1; dx<=1; dx++) {
		for(int dy = -1; dy<=1; dy++) {
			//nx,ny代表移動之後的位置
			int nx = x + dx,ny = y + dy;
			//當該位置在矩陣內且值為0時
			if(0<=nx&&nx<n&&0<=ny&&ny<m&& a[nx][ny] == 0 ) {
				//繼續DFS
				dfs(nx,ny);
			}
		}
	}
}

int main() {
	int sum = 0;
	for(int i=0; i<n; i++) {
		for(int j=0; j<m; j++) {
			if(a[i][j]==0) {
				dfs(i,j);
				sum++;
			}
		}
	}
	cout<<sum<<endl;
}

執行結果:

好了,本次淺談先到此為止,由於本人才學疏淺,文章中難免出現紕漏和錯誤,還望讀者能過指出,謝謝。