一,定義
通俗說DFS就是有路就走,一條道走到黑,走不通就回上一個分支的再選路,再不行就回上上個分支點......依次類推,最終找到一條通路。
例1:給定N個整數A1,A2 .....,判斷可否從中選出若干數,使它們的和恰好爲K。
思路:從第一個數開始,按順序決定每個數加還是不加,當對這個N個數全部決定完之後判斷它們的和是不是K就可以。
假設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;
}
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)極容易超限;同時,在上題中我們無法獲得是哪幾個元素湊成了K的解答,爲此,我們對上述代碼進行一點點優化。
優化代碼:
#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中,有時早已明確的知道無論怎麼選都不會存在解,在這種情況下不再繼續搜索而是略過,這種方法稱爲剪枝。