深度優先搜尋(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代替,執行一次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;
}
執行結果:
好了,本次淺談先到此為止,由於本人才學疏淺,文章中難免出現紕漏和錯誤,還望讀者能過指出,謝謝。