《算法笔记》8.2 深度优先搜索DFS

深度优先搜索是一种枚举所有完整路径以遍历所有情况的搜索方法,记住使用递归来实现DFS的本质其实还是栈——使用递归时,系统会调用系统栈 来存放递归中的每一层状态。

通过例子学习理解DFS

题目一

   有n件物品,每件物品的重量为w[i],价值为c[i],现在需要选出若干件物品放入一个容量为V的背包中,使得在选入背包的物品重量和不超过容量V的前提下,让背包中物品的价值之和最大,求最大价值,(1<n<20)

题解

   显然,每件物品都有装入背包与不装入背包两种选择,因此DFS函数的参数中必须记录当前处理的物品编号index。而题目中涉及了物品的重量和价值,因此也需要参数来记录在处理当前物品之前,已选物品的总重量sumW和总价值sumC。于是DFS函数看起来应该是这样子的:

void DFS(int index , int sumW , int sumC ){
	...
}

于是,如果选择不放入idenx号物品,那么sumW与sumC就将不变,接下来处理index+1号物品,即前往

DFS(index+1 , sumW , sumC)

这条分支;
如果选择放入index号物品,那么sumW需要加上w[index] , sumC需要加上c[index],即前往

DFS(index+1 , sumW+w[index] , sumC+c[index] )

这条分支。
   一旦index增长到n,则说明已经把n件物品处理完毕,此时记录的sumW和sumC就是所选物品的总重量和总价值。

代码

#include<bits/stdc++.h>
using namespace std ;

const int maxn = 30 ;
int n = 0, maxValue = 0 , v = 0 ; //n 物品总数, maxValue当前背包中物品价值总和, v 容量
int w[maxn] , c[maxn] ;  //w为每件物品的重量,c为每件物品的价值

void DFS(int index , int sumW , int sumC){
    //若当前物品是最后一件
    if( index == n){
        if( sumW <= v && sumC > maxValue ){
            maxValue = sumC ;
        }
        return ;
    }

    //每件物品都有两种选择——装与不装
    DFS(index+1 , sumW , sumC ) ;
    DFS(index+1 , sumW + w[index] , sumC+c[index]) ;

}


int main(){

    //输入物品总数,背包容量
    cin >> n >> v ;
    for(int i = 0 ; i < n ; i++){
        cin >> w[i] ;            //输入每件物品的重量
    }

    for(int i = 0 ; i < n ; i++){
        cin >> c[i] ;            //输入每件物品的价值
    }
    DFS(0,0,0) ;
    cout << maxValue <<endl ;

    return 0 ;
}

输入数据

5 8 	 //5件物品,背包容量为8
3 5 1 2 2   //重量分别为
4 5 2 1 3  //价值分别为

输出结果

10

算法优化

上面的代码复杂度为O(2^n),这看起来不是很优秀。再上述代码中,总是把n件物品处理之后才去更新最大价值,但是忽略了背包容量的限制。也就是说,可以把对sumW的判断放到分支前,只有sunW<=v时才能进入分支。

void DFS(int index , int sumW , int sumC){
	if(index == n){
		return ;   //已经完成n件物品的处理
	}
	
	DFS(index+1 , sumW , sumC) ;   //不选该物品
	//只有加入第index件物品后未超过背包容量v,才加入
	if(sumW +w[index] <= v){
        if(sumC + c[index] > maxValue ){
            maxValue = sumC+c[index] ;  //更新最大价值
        }
        DFS(index+1 , sumW+w[index] , sumC+c[index]) ;   //选第index件物品
	}
	
}

这种通过题目条件限制节省DFS计算量的方法称作剪枝

题目二

  给定N个整数(可能为负数),从中选择K个数,使得这K个数之和恰好等于一个给定的整数X ;如果有多种方案,选择他们中元素平方和最大的一个。数据保证这样的方案唯一。

思路

当试图进入“选index号数”这条分支时,就把A[index]加入temp中;而当这条分支结束时,就把他从temp中除去,使他不会影响“不选index号数”这条分支。接着,如果在某个时候发现当前已经选择了K个数,且这K个数之和恰好为sum时,就去判断平方和是否比已有的maxPow还要大:如果确实更大,就把temp赋给ans。

代码

#include<bits/stdc++.h>
using namespace std ;

const int maxn = 30 ;

//从序列A中N个数选择k个数使其和为x,最大平方和为maxSumSqu
int n , k , x , maxSumSqu = -1 , A[maxn] ;
//tmp 存放临时方案,ans存放平方和最大的方案
vector<int > tmp , ans ;

//当前处理index号整数,当前选择整数个数为nowK
//当前已选整数和为sum,当前已选整数平方和为sumSqu
void DFS(int index , int nowK , int sum , int sumSqu){
    if( nowK == k && sum == x ){
        if(sumSqu > maxSumSqu){
            maxSumSqu = sumSqu ;
            ans = tmp ;
        }
        return ;
    }

    //已经处理完N个数,或者选择的数超过k个数,或者所选整数之和大于x
    if(index == n || nowK > k || sum > x){
        return ;
    }

    //选当前数
    tmp.push_back(A[index]) ;
    DFS(index+1 , nowK+1 , sum + A[index] , sumSqu+A[index]*A[index] ) ;
    tmp.pop_back() ;
    //不选当前数
    DFS(index+1 , nowK , sum ,sumSqu) ;
}

int main(){
    cin >> n >> k >> x ;
    for(int i = 0 ; i < n ; i++){
        cin >> A[i] ;
    }

    DFS(0,0,0,0) ;

    for(int i = 0 ; i < ans.size() ; i++){
        cout << ans[i] << "  " ;
    }

    return 0 ;
}

输入样例

4 2 6	//从4个整数中选择2个数,使和为6
2 3 3 4	  //整数分别为

输出结果

2  4

  上面这个问题中的每个数都只能选择一次,现在稍微修改题目:假设N个整数中的每一个都可以被选择多次,那么选择K个数,使得K个数之和恰好为X,例如有三个整数1、 4, 7, 需要从中选择5个数,使得这5个数之和为17,显然,只需要选择3个1和2个7, 即可得到17。
  这个问题只需要对上面的代码进行少量的修改即可。由于每个整数都可以被选择多次, 因此当选择了index号数时,不应当直接进入index+1号数的处理。显然,应当能够继续选择index号数,直到某个时刻决定不再选择index号数,就会通过"不选index号数"这条分支进入index+1号数的处理。因此只需要把”选index号数"这条分支的代码修改为

DFS(index, nowK+ 1, sum+A[index], sumSqu+A[index] * A[index])

即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值