深度优先搜索

本文探讨了深度优先搜索(Depth First Search, DFS)在背包问题中的应用,通过枚举所有可能的物品组合,寻找最大价值的解决方案。并介绍了如何通过剪枝优化算法效率,以及如何使用DFS解决选择特定数量的整数以达到特定和的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

深度优先搜索是一种枚举所有完整路径以遍历所有情况的搜索方法。
背包问题:

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

模仿书中迷宫的例子寻找“岔路口”和“死胡同”,对每件物品都有选或者不选两个选择,这就是“岔路口”。而“迷宫的死胡同”是题目要求选择的物品重量总和不能超过V。所以一旦超过V,就需要返回最近的“岔路口”。
因为每次都要对物品进行选择,所以DFS()函数的参数中必须记录当前处理的物品编号index,而题目涉及了物品的重量和价值,因此也需要参数来记录在处理当前物品前已对物品的总重量sumW和总价值sumC。所以DFS函数声明:

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

如果不放入index号物品,那么sumW和sumC将不变,接下来处理index+1号物品,即前往DFS(index + 1, sumW, sumC)这条分支;而如果选择放入index号物品,那么sumW将增加当前物品的重量w[index],sumC将增加当前物品的价值c[index],然后处理index + 1号物品,即前往DFS(index + 1, sumW + w[index], sumC + c[index])这条分支。
一旦index等于n,则说明已经把n件物品处理完毕,此时记录sumW和sumC的就是所选物品的总重量和总价值。如果sumW不超过V且sumC大于一个全局的记录最大总价值的变量maxValue,就说明当前的这种选择方案可以得到更大的价值,于是用sumC更新maxValue。

#include <cstdio>
#include <cstdlib>
const int maxn = 30;
int n, V, maxValue = 0;
int w[maxn], c[maxn];
void DFS(int index, int sumW, int sunmC);
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);
	printf("%d\n", maxValue);
	system("pause");
	return 0;
}
void DFS(int index, int sumW, int sumC)
{
	if (index == n) //死胡同
	{
		if (sumW <= V && sumC > maxValue)
			maxValue = sumC; //更新最大值
		return;
	}
	DFS(index + 1, sumW, sumC); //不选第index件物品
	DFS(index + 1, sumW + w[index], sumC + c[index]); //选第index件物品
}

但是这段代码时间复杂度达到了O(2n),算是非常慢了。但是可以优化一下。
上面总是把n件物品的选择全部确定之后才去更新最大价值,但是事实上忽视了背包容量不超过V这个特点。这就是说,完全可以把对sumW的判断加入“岔路口”中,只有当sumW <= V时才进入岔道,这样效率会高很多:

void DFS(int index, int sumW, int sumC)
{
	if (index == 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]);
}

这里只有满足加入第index件物品后容量不超过V才能进入这条岔路,这样就降低了计算量。通过这种题目条件的限制来节省DFS计算量的方法叫做剪枝
上述问题给出了一类常见DFS问题,给定一个序列,枚举这个序列的所有子序列(可以不连续),从中选择一个最优子序列
比如:

给定N个整数,从中选择K个数,使K个数之和恰好等于一个给定的整数X;如果有多种方案,选择它们中元素平方和最大的一个。

比如4个整数{2, 3, 3, 4}中选择2个数,使它们和为6,显然有两种方案{2, 4}和{3, 3},其中平方和最大的方案是{2, 4}
此处仍需要继续当前处理的整数编号index;由于需要恰好选择K个数,因此需要一个参数nowK来记录当前已经选择的数的个数;另外,还需要参数sum和sumSqu分别记录当前已选整数之和和平方和,声明如下:

void DFS(int index, int nowK, int sum, int sumSqu) { ... }

这里用一个数组temp来存放当前已经选择的整数,这样,当试图进入“选index号数”这条分支时,就把A[index]放入temp中;当这条分支结束后,就把它从temp中去除,使它不会影响“不选index号数”这条分支。接着,如果某个时候发现当前已经选择了K个数,且这K个数之和恰好为x时,就去判断平方和是否比已有的最大平方和maxSumSqu还要大:如果确实更大,那么说明找到了更优的方案,把temp赋给用以存放最优方案的数组ans。这样,当所有方案都枚举完毕后,ans存放的就是最优方案,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) //k个数和为x
		{
			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.push_back();
	//不选index号数
	DFS(index + 1, nowK, sum, sumSqu);
}

上述问题如果改成假设N个整数中的每一个都可以被选择多次,那么选择K个数,使得K个数之和恰好为X
这时候只要做出一点修改即可,由于每个整数都可以被选择多次,因此当选择了index号数时,不应当直接进入index+1号。应该继续选择index号数,知道某个时刻决定不再选择index号数
因此只要把

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

改为:

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

即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值