关于DFS的一些个人理解

之前用DFS可能最多的就是树类问题,但是随着最近图论的深入,看了看相关的问题,发现问题并不局限于此;

由于之前接触过动态规划还有贪心算法,突然发现DFS和动态规划貌似有点类似,之前个人感觉可能不同的点在于两点:

1.动态规划有相关的状态转移方程,定下边界之后,就严格的按照状态转移方程来进行解决;

2.动态规划具有重叠子问题,进行逐步的计算,用小问题的解解决大问题。但是个人感觉,DFS貌似也有这种思想的体现,其具体体现的是回溯思想,所以这里脑壳还是有点转不过来;

但是我觉得以下的一位大佬总结的还是有点到位的,DFS其实也算是冬天规划的一种:

所以DFS思想不仅仅可以作为一个路径解决问题,更可以用来解决背包问题,并且用递归解决会有一个更加直观的表现;

在之前看的案例中就有这种体现;

例如:有n件物品,每件物品的重量位w[i],价值位c[i]。现在需要选出若干见物品放入一个容量V的背包中,使得选入背包的物品总重量不超过V的前提下,使得背包中的物品总价最大,并且求这个最大值;

对于这个问题,我们可以很好的规定出相关的边界;

递归边界显然是:总重量小于V,且迭代了n次,进行了2*n次选择;这里的选择很好理解,对于第i件物品,会有两种选择,放或者不放,可以类似认为像是走迷宫的两个分支,左边和右边;

因此,我们可以定义全局变量n(物品的件数),V(背包总容量),maxValue(背包内的物品最大值);

相关的函数定义可以为:

void DFS(int index,int sumW,int sumC)

其中sumW为现阶段下的总重量,sumC代表现阶段下的总价值;

void DFS(int index,int sumW,int sumC){
    if(index==n){
        //代表枚举n件物品完毕,递归边界
        if(sumW<=V&&sumC>maxValue){
            maxValue=sumC;
        }
        return;
    }
    DFS(index+1,sumW,sumC);
    DFS(index+1,sumW+w[index],sumC+c[index]);
}

整体来说DFS函数如上所示,函数结尾递归表示了两种选择,即选不选第i件;

上述算法的时间复杂度为O(2^n),表现不是很好,究其原因是其先选再判断容量,多了很多分支;

对于示例中给出的优化,为先判断在分治,会高效很多;

void DFS(int index,int sumW,int sumC){
    if(index==n){
        //代表枚举n件物品完毕,递归边界
        return;
    }
    DFS(index+1,sumW,sumC);
    if(sumW+w[index]<=V){
        if(sumC+c[index]>ans){
            ans=sumC+c[index];
        }
        DFS(index+1,sumW+w[index],sumC+c[index]);
    }
}

这可以说是比较经典的应用场景;

还有一个示例:给定N个整数(可能有复数),从中选择K个数,使得K个整数的和正好等于一个给定的整数X;如果有多个方案,选择他们平方和中最大的一个;

这个问题其实很好的阐述了回溯的思想,也就是中间状态当回溯的时候,该如何保存的问题;

如下代码所示,temp保存了当前的最优解选项,如果回溯,则进行pop操作,当符合最优解的时候,直接赋值保存;

int n,k,x,maxSumSqu=-1,A[maxn];
//从n中选择k个数,使得平方和最大化
vector<int>temp,ans;
void DFS(int index,int nowK,int sum,int sumSqu){
    if(nowK==k&&sum==x){
        if(sumSqu>maxSumSqu){//如果找到最优解
            maxSumSqu=sumSqu;//更新最大平方和
            ans=temp;//更新最优方案
        }
        return;
    }
    if(index==n||nowK>k||sum>x)
        return;
    temp.push_back(A[index]);
    DFS(index+1,nowK+1,sum+A[index],sumSqu+A[index]*A[index]);
    temp.pop();
    DFS(index+1,nowK,sum,sumSqu);
}

这里注意一下,这个题目其实和01背包问题类似,所以如果对于普通背包问题,即可以选择重复的数字时,又该怎么办;

解决方案如下所示,和背包的状态转移类似,如果可以选择自己,则状态不变,类似的,DFS可以使得index不变,从而表示再一次选择该数字;

int n,k,x,maxSumSqu=-1,A[maxn];
//从n中选择k个数,使得平方和最大化
vector<int>temp,ans;
void DFS(int index,int nowK,int sum,int sumSqu){
    if(nowK==k&&sum==x){
        if(sumSqu>maxSumSqu){//如果找到最优解
            maxSumSqu=sumSqu;//更新最大平方和
            ans=temp;//更新最优方案
        }
        return;
    }
    if(index==n||nowK>k||sum>x)
        return;
    temp.push_back(A[index]);
    DFS(index,nowK+1,sum+A[index],sumSqu+A[index]*A[index]);
    temp.pop();
    DFS(index+1,nowK,sum,sumSqu);
}

猜你喜欢

转载自blog.csdn.net/InNoVaion_yu/article/details/86743062