关于算法笔记第八章的总结(DFS )

DFS

DFS可以用栈来实现,但是用栈实现较为麻烦,所以采用递归来进行实现:
1)递归式就是岔道口
2)递归边界就是死胡同

讲解例子,理解DFS思想:

有n件物品,每件物品的重量为w[i],价值为c[i],现在要选出若干件放入容量为V的背包中,为使背包价值和最大,求最大价值( 1 <= n <= 20

分析:

1.“岔路口” 和 “死胡同”分别是什么?
每件物品都有选或者不选两种选择–岔路口
一旦选择的物品超过了V,就到达死胡同

2.DFS函数的参数怎么写
a.需要记录当前要处理物品的编号;
b.需要在处理当前物品之前,选物品的总重量sumW 和 总价值sumC
因此:

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

3.岔路口的处理
如果不放入index号物品,则sumW 和 sumC不变,接下来处理index + 1号物品,即前往DFS (index + 1,sumW,sumC)这条分支
如果放入,则前往DFS (index + 1,sumW + w[index], sumC + c[index])

4.死胡同的处理
当index增加到了n,则说明已经把n件物品处理完毕,如果sumW <= V 且sumC >一个全局记录最大总价值变量maxValue,就说嘛当前这种选择方案可以得到更大的价值,于是用 sumC 更新maxValue.

代码实现:

#include <cstdio>
int n, V, maxValue = 0;						//物品件数n,背包容量V,最大价值maxValue
const int Maxn = 30;
int w[Maxn],c[Maxn];						//w[i]为每件物品的重量,c[i]为每件物品的价值
//DFS,index为当前处理物品的编号
//sumW,sumC分别为当前总重量和总价值
void DFS(int index, int sumW, int sumC){
	//已经完成对n件物品的选择(死胡同)
    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(){
    scanf("%d%d",&n,&V);
    for(int i = 0; i < n; i++){
        scanf("%d",&w[i]);
    }
    for(int i = 0; i < n; i++){
        scanf("%d",&c[i]);
    }
    DFS(0,0,0);								//初始为第0件物品,当前总重量和总价值为0
    printf("%d\n",maxValue);
    return 0;
}
/*
5 8
3 5 1 2 2
4 5 2 1 3
 */

进一步分析,以上代码复杂度为O(2^n),则考虑对该算法进行优化。

上诉代码:
把n件物品全部确定后才更新最大值,忽略了背包容量不超过V这个特点。
改进:
把对sumW的判断 移动 入“岔道口”中,只有当sum<=V时才进入岔道

改进如下:

void DFS(int index, int sumW, int sumC) {
	if(index == n){
		return;
	}
	DFS(index + 1, sumW, sumC);					//不选第index件商品
	//只有加入第index件物品后未超过容量V,才能继续
	if(sumW + w[index] <= V) {
		if(ans < sumC + c[index]) {
			ans = sumC + c[index];				//更新最大价值maxValue
		}
		DFS(index + 1,sumW + w[index], sumC + c[index]);	//选第index件物品
	}
}

分析:原先第二条岔路是直接进入的,但是这里有条件满足是才能进入,可以降低计算量,使算法在数据不极端时有很好的表现–剪枝

一类常见的DFS问题的解决方法:给定一个序列,枚举这个序列的所有子序列(可以不连续),从中选择一个“最优”子序列,使它的某个特征是所有子序列中最优的。

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

分析:
1.DFS的参数:
处理当前整数的编号index,由于需要选择k个数,因此需要一个参数nowK来记录当前已经选择的个数;还需要记录当前已选整数的和sum和平方和sumSqu:

void DFS(int index, int nowK, int sum, int sumSqu)

2.需要一个整数数组temp,用以存放当前已经选择的整数,所以:
1)当试图进入“选index号数”这条分支时,就把A[index]加入temp中
2)上条分支结束时没需要把它从temp中去除

3.当某个时候发现了已经选择了k个数,并且这k个数之和恰好为x时,就判断平方和是否比已经得到的最大平方和更大:
如果更大,则用temp附给存放最优方案的数组ans.

4.当所有方案都枚举完毕后,ans存放的就是最优方案,maxSumSqu就是对应的最优值

//序列A中n个数选k个使得和为x,最大平方和为maxSumSqu
int n,k,x,maxSumSqu = -1,A[maxn];
//temp存放临时方案,ans存放平方和最大的方案
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;
	}
	//已经超过k个数,或者已经超过x,或已经处理完n,返回
	if(nowK > k || index == n || sum > x) return;
	//选index号数
	temp.push_back(A[index]);
	DFS(index + 1, nowK + 1, sum + A[index], sumSqu + A[index] * A[index]);
	temp.pop_back();
	//不选index号数
	DFS(index + 1,nowK, sum, sumSqu);
}

修改问题:
上诉问题每个数字只能选一次,假设每个数字都能选多次,如何修改代码?

当选择了index号数时,不应该直接进入index+1号数的处理,应该继续选择index号数,直到某个时刻决定不再选index号数,再进行index + 1的处理
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gylagyl97

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值