栈的应用之背包问题

背景

使用递归函数都可以用局部栈保存中间信息的非递归函数来解决,相应的,任何一个包含循环的程序都可以翻译为一个不包含循环的递归函数。

如果遇到一个递归算法,希望做出它的一个非递归实现,更合适的方法是分析算法的具体情况,弄清楚计算的细节,然后根据得到的认识自己设计出相应的非递归函数。

只有一个递归调用时,这种形式的递归函数定义很容易翻译成非递归的函数定义。如果函数中出现了多个递归调用,算法的翻译将复杂许多,但还是可以完成的。

经典算法问题背包问题的递归解法中使用了两个递归调用,可以通过两层循环翻译成非递归函数。

问题描述

一个背包的总容量为weight, 现有n件物品的集合为S,其中物品的重量分别为w0, w1, … , wn-1. 问题是能否从中选出若干件物品,其重量之和正好等于weight.

思路

递归和非递归解法的思路都是

  1. 对物品列表进行扫描时先贪心,如果遇到的物品放入背包后背包不满,则放入背包,如果遇到的物品重量太大,放入后背包超重,则跳过该物品,扫描下一个,直到最后一个物品
  2. 如果中途背包正好填满,则break,输出背包中的物品
  3. 如果直到最后一个物品都没有使背包填满,则掏出最近放入的物品,对于下一个物品重复上述操作
  4. 如果进行到步骤3,想要掏出最近放入的物品时,发现背包空了,则说明失败,不存在一个物品组合重量恰好等于背包容量。

仔细想一想,由于每一个物品放/不放相当于一个二叉树结构,背包中最近放入的物品就相当于储存的结点。当背包空了,就所有可能的结点都走完来,没有可以尝试的退路了。而当我们成功break时,背包中的物品就是解法。

递归解法

递归解法如果利用分治思想来理解,则很简单:
从最后一个物品倒着开始检查
对于第n个物品,此时背包容量为w

  1. 如果w为0,说明第n+1到最后一个物品存在解法组合,回退输出
  2. 如果此时w<0或n<0,则说明目前的解法路径失败,回退
  3. 如果该物品放入后,背包容量减少到w-w[n],前n-1个物品存在恰好填满背包的解法,则该物品可以放入
  4. 如果该物品不放入,背包容量不减少,依然为w,前n-1个物品存在恰好填满的解法,则该物品可以不放入
  5. 如果3,4都不成立,则说明目前的解法路径失败,回退
代码
weight=19
S=[2,5,9,8,7,11,5]
n=len(S)-1
def recursion(w,s,n):
    if w==0:
        return True
    if w<0 or n<0:
        return False
    if recursion(w-s[n],s,n-1):
        print(n)
        return True
    if recursion(w,s,n-1):
        return True
    else:
        return False
recursion(weight, S, n)

栈解法

栈解法稍微有些复杂,不同于递归的分治理解,使用栈需要想清楚具体的细节。我的思考分为以下三部分:

  • 内层循环
    内层循环就是从一个‘结点’开始到最后一个物品的扫描,如果遇到放入后还填不满的物品则放入背包,如果遇到过大放入后超重的物品则跳过进行下一个物品的检查,如果遇到放入后恰好填满的,则成功,return目前的栈,栈内保存着正确解法物品组合的序号。
    代码如下:
while i<=n-1:
            if s[i]+total<w:
                stack.append(i)
                total+=s[i]
                i+=1
            elif s[i]+total==w:
                stack.append(i)
                return stack
            else:
                i+=1
  • 弹栈操作和终止条件
    如果内层循环结束后,依然没有退出、return,说明目前解法路径不存在正确组合,回退,弹出栈顶‘结点’,注意此时也要对计重变量total减去拿出的物品重量
    代码:
    while stack:
        j=stack.pop()
        total-=s[j]
        i=j+1
  • 初始化
    初始化操作中,我们需要考虑代码的起步,需要放入一个元素到栈中,从而开始while循环。同时在进入内层循环前,i要等于0,total要为0
    所以将-1放入stack,total=s[-1].
    stack=[-1]
    total=s[-1]
    n=len(s)

全部代码如下;

weight=19
S=[2,5,9,8,7,11,5]
def pack(w, s):
    stack=[-1]
    total=s[-1]
    n=len(s)
    while stack:
        j=stack.pop()
        total-=s[j]
        i=j+1
        while i<=n-1:
            if s[i]+total<w:
                stack.append(i)
                total+=s[i]
                i+=1
            elif s[i]+total==w:
                stack.append(i)
                return stack
            else:
                i+=1
    print ("Error")

pack(weight,S)

总结

很多经典问题比如DFS(深度空间搜索)同样可以用递归和栈两种方法解决,相比递归,栈的优势是保存所有路径‘结点’,而相比栈,递归的优点是回退更有顺序性,不需要考虑记录结点的问题。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值