牛牛背包问题----三种解法详解

题目描述

牛牛准备参加学校组织的春游, 出发前牛牛准备往背包里装入一些零食, 牛牛的背包容量为w。
牛牛家里一共有n袋零食, 第i袋零食体积为v[i]。
牛牛想知道在总体积不超过背包容量的情况下,他一共有多少种零食放法(总体积为0也算一种放法)。

输入描述
输入包括两行
第一行为两个正整数 n n n w ( 1 < = n < = 30 , 1 < = w < = 2 ∗ 1 0 9 ) w(1 <= n <= 30, 1 <= w <= 2 * 10^9) w(1<=n<=30,1<=w<=2109),表示零食的数量和背包的容量。
第二行 n n n个正整数 v [ i ] ( 0 < = v [ i ] < = 1 0 9 ) v[i](0 <= v[i] <= 10^9) v[i](0<=v[i]<=109),表示每袋零食的体积。
输出描述
输出一个正整数, 表示牛牛一共有多少种零食放法。
示例
输入:
3 10
1 2 4
输出:
8

解法一:暴力求解

N N N个零食就有 2 N 2^N 2N种防治方法,遍历这些放置方法,检查哪些方法是有效的,时间复杂度为 O ( N ∗ 2 N ) O(N*2^N) O(N2N),复杂度相当高,当然也没有被ac。
这里涉及到一个问题就是如何判断方法是否有效,以示例为例来说,3个零食就有8中方法,1~4的二进制表示为:

1: 001 ---- 第一个零食放下
2: 010 ---- 第二个零食放下
3: 011 ---- 第一、二个零食被放下
4: 100 ---- 第三个零食放下

可以看到二进制表示中的每位上的1就代表了第几个零食应该放下,因此我们只需要去判断放下这些零食是否会超出存储空间即可。

if __name__ == '__main__':
    n, w = map(int, input().split())
    v = list(map(int, input().split()))

    v_new = []
    for i in v:
        if i <= w:
            v_new.append(i)

    n = len(v_new)
    total_num = 1<<n
    res = 0
    for i in range(total_num):
        left = w
        j = 0
        while j < n and left >= 0:
            if i & (1<<j):
                left -= v[j]
            j += 1
        if left >= 0: res += 1
    print(res)

解法二:中途相遇法

使用中途相遇法的思想,将 N N N份零食一分两半,枚举第一份的所有取法,用一个体积数组记录体积和,再枚举第二份的所有取法,从体积数组中找到大于等于
第二份体积的第一个位置,就是所有的取法。


if __name__ == '__main__':
    import bisect
    n, w = map(int, input().split())
    v = list(map(int, input().split()))

    v_new = []
    for i in v:
        if i <= w:
            v_new.append(i)

    n = len(v_new)
    up = n // 2
    down = n - n//2
    space = []
    res = 0

    total_up = 1<<up
    for i in range(total_up):
        left = w
        j = 0
        while j < up and left >= 0:
            if i & (1<<j):
                left -= v[j]
            j += 1
        if left >= 0:
            space.append(w-left)
    space.sort()

    total_down = 1<<down
    for i in range(total_down):
        left = w
        j = 0
        while j < down and left >= 0:
            if i & (1<<j):
                left -= v[j+up]
            j += 1
        if left >= 0:
            # res = bisect.bisect_right(space, left) - space[0]
            res += bisect.bisect_right(space, left)
    print(res)

这里需要说明一下为什么res += bisect.bisect_right(space, left):
space数组中存储的是第一份数组的存储空间,而第二份数组的存储空间在space中找到相应的位置就代表着有多少种取法。

解法三:深度搜索法(DFS)

def dfs(idx, n, visited, count, left, v):
    if idx == n:
        return
    for i in range(idx, n):
        if not visited[i] and left >= v[i]:  #当前点没有被访问且存储空间能够装下
            visited[i] = True
            count += 1
            left -= v[i]
            #继续往后搜索看是否有符合条件的点
            dfs(i+1, n, visited, count, left, v) 
            visited[i] = False  #释放当前的点
            left += v[i]      #释放存储空间

if __name__ == '__main__':
    n, w = map(int, input().split())
    v = list(map(int, input().split()))

    if sum(v) <= w:
        print(1<<n)
    else:
        visited = [False]*n
        count = 1
        left = w
        dfs(0, n, visited, count, left, v)
        print(count)

其实这里有个坑,就是count=[1],如果count=1就会出错,至今我也没弄清楚这个bug的缘由。
此外,这里也给出dfs的一个伪代码:

/**
 * DFS核心伪代码
 * 前置条件是visit数组全部设置成false
 * @param n 当前开始搜索的节点
 * @param d 当前到达的深度,也即是路径长度
 * @return 是否有解
 */
bool DFS(Node n, int d){
	if (d == 4){//路径长度为返回true,表示此次搜索有解
		return true;
	}
 
	for (Node nextNode in n){//遍历跟节点n相邻的节点nextNode,
		if (!visit[nextNode]){//未访问过的节点才能继续搜索
 
			//例如搜索到V1了,那么V1要设置成已访问
			visit[nextNode] = true;
 
			//接下来要从V1开始继续访问了,路径长度当然要加
 
			if (DFS(nextNode, d+1)){//如果搜索出有解
				//例如到了V6,找到解了,你必须一层一层递归的告诉上层已经找到解
				return true;
			}
 
			//重新设置成未访问,因为它有可能出现在下一次搜索的别的路径中
			visit[nextNode] = false;
 
		}
		//到这里,发现本次搜索还没找到解,那就要从当前节点的下一个节点开始搜索。
	}
	return false;//本次搜索无解

你可能感兴趣的:(笔试编程)