一,定义
通俗说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中,有时早已明确的知道无论怎么选都不会存在解,在这种情况下不再继续搜索而是略过,这种方法称为剪枝。