分支定界(Branch&bound)算法


背包問題,一般可以用動態規划解決。當涉及到的物體數目比較多,填表法所需要的存儲空間很大$O(nW)$,每次都以內存不足告終。

參考:

https://www.geeksforgeeks.org/implementation-of-0-1-knapsack-using-branch-and-bound/

1.填表法:

def solve_it(input_data):
    # Modify this code to run your optimization algorithm

    # parse the input
    lines = input_data.split('\n')

    firstLine = lines[0].split()
    item_count = int(firstLine[0])
    capacity = int(firstLine[1])

    items = []

    for i in range(1, item_count+1):
        line = lines[i]
        parts = line.split()
        items.append(Item(i-1, int(parts[0]), int(parts[1])))

    #print item information
   # for i in range(0, len(items)):
    #    print str(items[i].index) + ',' + str(items[i].value) + ',' + str(items[i].weight) +'\n'

    # a trivial greedy algorithm for filling the knapsack
    # it takes items in-order until the knapsack is full
    value = 0
    weight = 0
    taken = [0]*len(items) #為1則表示選擇了該物體
    '''
    for item in items:
        if weight + item.weight <= capacity:
            taken[item.index] = 1
            value += item.value
            weight += item.weight
    '''
    
    result = np.zeros([capacity+1, item_count+1]) #表的大小為n*W,n為物體數目,W為包的容量
    #result[k][0] = 0 for all k
    for i in range(0, capacity+1):
        result[i][0] = 0
    for i in range(0, item_count+1):
        result[0][i] = 0
    #填表法
    for k in range(1, capacity+1):
        for j in range(1, item_count+1): #第j件物品其索引值為j-1
            if k-items[j-1].weight >= 0:
                result[k][j] =  max([result[k][j-1], result[k-items[j-1].weight][j-1] + items[j-1].value])
            else:
                result[k][j] = result[k][j-1]
    value = int(result[capacity][item_count])
    out_to_csv(result)
    #根據表尋找最優解的路徑
    k = capacity
    j = item_count
    while(result[k,j] != 0):
        if result[k][j] > result[k][j-1]:
            k = k - items[j-1].weight
            taken[j-1] = 1
            j = j - 1
        else:
            j = j - 1
        
            


    # prepare the solution in the specified output format
    output_data = str(value) + ' ' + str(0) + '\n'
    output_data += ' '.join(map(str, taken))
    return output_data

填表法在物體數目較小的時候可以解決,單所需表的存儲空間比較大的時候開始報錯。

故選擇了分支定界算法。

2. 關於python3中自定義比較函數的用法:

參考自:https://www.polarxiong.com/archives/Python3-%E6%89%BE%E5%9B%9Esort-%E4%B8%AD%E6%B6%88%E5%A4%B1%E7%9A%84cmp%E5%8F%82%E6%95%B0.html 

from functools import cmp_to_key

nums = [1, 3, 2, 4]
nums.sort(key=cmp_to_key(lambda a, b: a - b))
print(nums)  # [1, 2, 3, 4]

2.1 我的自定義比較函數:

from functools import cmp_to_key
#自定義比較函數
def mycmp(item1, item2): 
    if(item1.value*1.0/item1.weight > item2.value*1.0/item2.weight): #value/weight大的排前邊
        return -1
    else:
        return 0
#關於python3的自定義比較函數用法
items.sort(key=cmp_to_key(lambda a, b : mycmp(a,b))) 

2.2 用到了節點類:

#節點類        
class Node:
    def __init__(self, level, curvalue, room, bestvalue, taken, capacity): #成員變量
        self.level = level 
        self.curvalue = curvalue
        self.room = room
        self.bestvalue = bestvalue
        self.path = taken
        self.capacity = capacity

    def show(self):
        print(self.level , ",", self.curvalue, ",", self.room, "," , self.bestvalue)
    #所求的bound值
    def bound(self, items):
        weight = 0
        value = 0
        if self.level == -1:
            for i in range(len(items)):
                if weight + items[i].weight <= self.capacity:
                    value += items[i].value
                    weight += items[i].weight
                else:
                    value += (self.capacity - weight) * 1.0 / items[i].weight * items[i].value
                    break
        else:
            value += self.curvalue
            weight += self.capacity - self.room
            for i in range(self.level+1, len(items), 1):
                if weight + items[i].weight <= self.capacity:
                    value += items[i].value
                    weight += items[i].weight
                else:
                    value += (self.capacity - weight) * 1.0 / items[i].weight * items[i].value
                    break
        return value

3. 深度優先的分支定界。用棧實現,未用遞歸。

def solve_it(input_data):
    # Modify this code to run your optimization algorithm

    # parse the input
    lines = input_data.split('\n')

    firstLine = lines[0].split()
    item_count = int(firstLine[0]) #物體數目
    capacity = int(firstLine[1]) #背包容量
    items = []  
    
    for i in range(1, item_count+1):
        line = lines[i]
        parts = line.split()
        items.append(Item(i-1, int(parts[0]), int(parts[1]))) #物體初始化

  
    value = 0
    weight = 0
    taken = [0]*len(items)  
    empty = [0]*len(items)    
    #關於python3的自定義比較函數用法
    items.sort(key=cmp_to_key(lambda a, b : mycmp(a,b))) 
    
  #  for item in items:
   #     print (str(item.index) + "," + str(item.value) + "," + str(item.weight))
    
    stack = [] #深度優先用棧實現,python中list代替
    u = Node(-1, 0, capacity, 0, empty, capacity)
    temp = u.bound(items)
    u.bestvalue = temp
   # print("curvalue:", u.curvalue) 
    #print("bound:", u.bestvalue)
    stack.append(u)
    max_profit = 0
    while(len(stack) != 0):
        #彈出末尾的節點
        t = stack.pop() 
        v = Node(-1, 0, capacity, 0, empty, capacity)
        if t.level == -1:
            v.level = 0
        if t.level == item_count-1:
            continue
        #not choose this item
        v.level = t.level + 1
        v.room = t.room
        v.curvalue = t.curvalue
        v.bestvalue = v.bound(items)
        v.path = t.path.copy() #注意Python中list為引用,故不能直接賦值,而是用copy()方法
        if v.bestvalue > max_profit:
            stack.append(v)
            if v.level == item_count -1:
                max_profit = v.curvalue #保留最大profit
                taken = v.path #保留最優解

        #choose this item
        v1 = Node(-1, 0, capacity, 0, empty, capacity)
        v1.level = t.level + 1
        v1.room = t.room - items[v1.level].weight
        v1.curvalue = t.curvalue + items[v1.level].value
#        print("curvalue:", v1.curvalue) 
        #copy(), 不能直接賦值,因為都是引用
        v1.path = t.path.copy() 
        v1.path[items[v1.level].index] = 1
        v1.bestvalue = t.bestvalue
#        print("v1.path:", v1.path)
        if (v1.room >= 0) and (v1.bestvalue > max_profit):
   #         print(taken)
            #滿足則加入stack
            stack.append(v1)
            if v1.level == item_count-1:
                max_profit = v1.curvalue #保留最大profit
                taken = v1.path #保留最優解集
              #  print(taken)
    value = max_profit

    #prepare the solution in the specified output format
    output_data = str(value) + ' ' + str(0) + '\n'
    output_data += ' '.join(map(str, taken))
    return output_data

4.總結

第一次做分支定界算法,總算解決了問題。第一遍寫的實現問題百出,首先是bound的計算問題,當bound計算出錯時,會發現樹無法高效地剪枝(pruning)。導致程序一直運行。后來才發現是bound的計算錯誤。改正bug后,程序完成的時間都不到一分鍾。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM