回溯法(深度优先)剪枝和分支限界法(宽度优先)剪枝对比:01背包问题

限界函数:

CurValue + rest <= BestValue

回溯法(深度优先)剪枝

# 递归方式
class pack_01_back_prune_test:        
    def __init__(self,N,V,C,W):
        self.num =N
        self.V = V
        self.C = C
        self.W = W
        self.BestResult = [False]*N
        self.Selected = [False]*N
        self.BestValue = 0
        self.CurCost = 0
        self.CurValue = 0
        # bound()限界函数
        self.rest = 0
        for i in range(N):
            self.rest += W[i]
        
    
    def pack_01_back_tracking(self,depth):
        
        if depth > self.num-1:
            if self.CurValue > self.BestValue:
                self.BestValue = self.CurValue               
                self.BestResult[:] = self.Selected[:]

        else:
            # 满足约束条件和限界函数的处理
            if self.CurCost + self.C[depth] <= self.V and self.CurValue + self.rest > self.BestValue:
                self.Selected[depth] = True
                
                self.CurCost += self.C[depth]
                self.CurValue  += self.W[depth]
                self.rest -= self.W[depth]
                # next
                self.pack_01_back_tracking(depth+1)
                # undo
                self.CurCost -= self.C[depth]
                self.CurValue  -= self.W[depth]
                self.rest += self.W[depth]
            # 满足限界函数   
            if self.CurValue + self.rest > self.BestValue:
                self.Selected[depth] = False
                self.pack_01_back_tracking(depth+1)
        
    def print_Result(self):
        self.pack_01_back_tracking(0)
        print(self.BestResult)
        print(self.BestValue)                

# 迭代方式     
#%%
# 这种解法注意回溯函数一致性,思路比较清晰:满足剪枝条件就回溯:CurValue + rest <= BestValue
# 这是基于深度优先搜索的回溯法剪枝,以下是迭代实现方法
def pack_01_back_prune_iteration_test(N,V,C,W):
    depth = 0
    BestResult = [False]*N
    Selected = [False]*(N)
    BestValue = 0
    CurCost = 0
    CurValue = 0   
    # bound()限界函数
    rest = 0
    for i in range(N):
        rest += W[i]
    
    while True:
        # 尽量向左走直到不满足约束条件
        while depth < N and CurCost + C[depth] <= V:
            rest -=W[depth]
            Selected[depth] = True
            CurCost += C[depth]
            CurValue  += W[depth]
            depth +=1
        # 走到底,结果处理
        if depth >= N:
            BestValue = CurValue                
            BestResult[:] = Selected[:]
        # 不能往左走,就向右走,注意这里只是走一步而已
        else:
            rest -=W[depth]
            Selected[depth] =False
            depth +=1
        
        # 当不满足限界函数的时候,就需要回溯,注意底部也满足这个条件
        while CurValue + rest <= BestValue:
            # 回溯的处理,之所有需要depth -=1,上面走的时候都depth++了,底部也是这样
            depth -=1
            while depth >=0 and not Selected[depth]:
                rest +=W[depth]
                depth -=1
            # 当回溯到root的之后,无法回溯了,输出结果
            if depth < 0:
                return BestResult,BestValue
            # 回溯恢复现场
            else:
                Selected[depth] =False
                CurCost -= C[depth]
                CurValue  -= W[depth]
                depth +=1  
# 运行
N = 8
V = 30
C = [11,2,3,9,13,6,15,7,19]
W = [5.0,2.0,5.0,7.0,5.0,11.0,6.0,14.0]

print(pack_01_back_prune_iteration_test(N,V,C,W))

pack_01_back_prune_test(N,V,C,W).print_Result()       



([False, True, True, True, False, True, False, True], 39.0)
[False, True, True, True, False, True, False, True]
39.0  

分支限界法(宽度优先)剪枝

也使用一样的限界函数:
CurValue + rest <= BestValue
回溯法深度优先很快就能得到一个可行解,进而可以更新BestValue,分支限界法宽度优先,可行解都在最后一层,BestValue一直得不到更新,那就是限界函数一直不起作用
为了使限界函数早生效,我们应该提前更新BestValue,在满足约束条件下,进入左子树就可以更新BestValue,因为最终的BestValue是满足约束条件下Curvalue里面的最大值

代码实现:

#%%
class FIFO_01_Pack_prune:
    def __init__(self,N,V,C,W):
        self.num =N
        self.Volume = V
        self.Cost = C
        self.Value = W
        self.BestValue = 0
        
        #用于存放活结点,便于理解,把根结点,以及第0层结束标志-1放进去
        # 结点包括2个属性:当前空间大小,当前的价值大小
        self.queue = [[0,0],[-1,-1],]  
        # 当前剩余价值和,bound()限界函数
        self.rest = 0
        # 把第一个减去,因为我们要在进入这一层前更新rest
        for i in range(1,N):
            self.rest += W[i]
        

    # 实现时叶子结点不加入到活结点列表
    def enQueen(self,pair,depth):
        if depth < self.num -1:
            self.queue.append(pair)
            
    def pack_01(self):
#        selected = [0]*self.num      
        # 首先取出根结点
        depth = 0
        pair = self.queue.pop(0)
        CurCost = pair[0]
        CurValue = pair[1]
        
        while True:
            # 判断左结点能否加入到队列,能的话,把当前空间和当前价值放入队列,满足约束条件
            if CurCost + self.Cost[depth] < self.Volume:
                # 满足限界函数
                if CurValue + self.Value[depth] + self.rest > self.BestValue:
                    # 在进入左子树时,更新bestvalue
                    self.BestValue = CurValue + self.Value[depth]                   
                self.enQueen([CurCost + self.Cost[depth],CurValue + self.Value[depth]],depth)
            
            # 右满足限界函数
            if CurValue + self.Value[depth] + self.rest > self.BestValue:
                self.enQueen([CurCost,CurValue],depth)
            
            # 然后弹出下一个结点
            pair = self.queue.pop(0)
            CurCost = pair[0]
            CurValue = pair[1]
            
            # 当同一层处理完毕时,先判断是否能够输出结果,判断的标准是队列是否为空,
            # 这时下一层的所有结点已经加入了队列,这时需要把下一层
            # 增加一个结尾-1便于判断,然后进入下一层,弹出下一个结点
            if CurCost == -1:
                if not self.queue:
                    return self.BestValue
                self.enQueen([-1,-1],depth)
                depth += 1
                # 在刚进入下一层时,更新rest
                self.rest -= self.Value[depth]
                # 弹出下一个结点
                pair = self.queue.pop(0)
                CurCost = pair[0]
                CurValue = pair[1]
    
    def print_Result(self):
        print(self.pack_01())                
    
#%%
N = 8
V = 30
C = [11,2,3,9,13,6,15,7,19]
W = [5.0,2.0,5.0,7.0,5.0,11.0,6.0,14.0]

#pack_01_back_test(N,V,C,W).print_Result()
FIFO_01_Pack_prune(N,V,C,W).print_Result()

不知道大家注意到没有?上述实现方式没有使用单位体积价值的排序,和之前提到01背包回溯法基于单位体积价值实现不一样(先装单位体积价值高的)。

猜你喜欢

转载自blog.csdn.net/weixin_40759186/article/details/84675277
今日推荐