背包問題為一個典型的最佳化問題,想像你來到了一個寶庫,裡頭有一些寶物,都有各自的價值和重量,但你只帶了一個背包(而且負重還有限制),要怎麼取寶物才能在背得走的前提下,帶走價值總和儘可能高的寶物們呢?
這裡我們考慮最基本的 0/1 - Knapsack Problem 。簡單的 formulation 如下:
We have items, each one with value and weight . The weight limit of the backpack is .
2-Approx Algorithm
若今天物品是可分割的話,有一個簡單的 greedy 方法可以找到最佳解 (依據作排序,選擇單位重量價值最高的物品,塞到背包塞不下為止)。而在 0/1 - Knapsack 中,也可利用此方法找到 2-Approx 的解。
Algorithm
Sort items according to in decending order. Choose items in that order until constraint DOESN'T satisfy, then pick
DP-based Algorithm
概念是給定欲達到的價值(),從物品的子集(item 1 ~ i)中,找到所需重量最小的物品集合之重量 ( )
Algorithm
Let : min. weight chosen from items 1 ~ i such that
initial condition:
recursive relation:
Then search the n-th column (along the value axis),
we can find
The time complexity is
然而,我們已知 Knapsack Problem 為 NPC 問題,目前並不存在已知的 polynomial-time 的解法,但為何 DP 的複雜度卻不是指數呢?
Psuedo-Poly Algorithm [Weakly NP-Hard]
Running time is poly. in the input size under unary representation (not binary )
一般而言,我們會以二進位制儲存數字,所以 input size 應為 ,然而方才的演算法卻會一個一個地掃過 value axis ,直到找到解為止,最壞情況下(相當寬鬆),複雜度為
Running time 為 input 的指數。\
(另外,若想用 unary representation 來儲存 instance ,付出的代價即是空間複雜度變為
原先input size 的指數)
Strong NP-Hard
NP-Hard even all numerical values are POLY(sizeof input) e.g Exactly Set Cover
(1+ )-Approx Algorithm
剛剛 DP 的方法壞就壞在必須線性掃過 value axis, 我們是否能過不要檢查每一個 ,而是跳著檢查某些 值就好了呢?從上述的直覺,發展出 rounding 這個 scheme,犧牲一些精確度(),將複雜度壓回 polynomial。
Algorithm
Let
Now, consider the rounded instance ,
Running time for solving is
Correctness Check
value(OPT) in 0-1 Knapsack\
value(OPT) in 進位)\
() in \
() in 0-1 Knapsack + (直接就加一
個刻度)\
= value() +
value() value(OPT)\
value() value(OPT) ( )
Improvement version
上題的 可以想成離散化 值的刻度大小,我們可否在保持( ) - approx. ratio 的前提下,跳大格一點( 取大一點)。從上式的推導可以發現,若想保持 approx. ratio , 必須小於 value(OPT),而想降低 running 的 order , 需要跟 差不多 order 。也就是我們需要估計一個比 value(OPT) 小,但不會差太多的 , 。 (而且估計的 running time 不能高於之後 derive 出來的複雜度)
Greedy Algorithm to estimate
Poly-time Approximation Scheme (PTAS)
Given , can find ()-approx. algorithm w/ running time is poly. for constant
Fully Poly-time Approximation Scheme (FPTAS)
Given , can find ()-approx. algorithm w/ running time is poly. for constant and \
e.g 剛剛提到的 Rounded DP-based Algorithm
Psuedo-Poly Implementation
/**************************************************************************** | |
FileName [ knapsack.h ] | |
Synopsis [ Knapsack Solver ] | |
Author [ Jui-Yang (Tony) Hsu] | |
Copyright [ Copyleft(c) 2017 NTUEE, Taiwan ] | |
****************************************************************************/ | |
#ifndef KNAPSACK_H | |
#define KNAPSACK_H | |
#include <vector> | |
#include <algorithm> | |
/** | |
* Note: weight type must be integer | |
**/ | |
namespace knapsack{ | |
struct Item{ | |
int value; | |
int weight; | |
Item(int value=0,int weight=0): value(value),weight(weight){} | |
}; | |
int value_accumulate(const Item& lhs, const Item& rhs){return lhs.value + rhs.value;} | |
//Given the item list (item w/ weight and value) and capacity constraint | |
//Returns an integer vector representing the picked item's id of OPT soltion | |
//D{[n,v]: min. weight of picked item (1 ~ n) s.t value >= v | |
std::vector<int> knapsack_solver(std::vector<Item>& item_ls, int cap){ | |
int max_val = std::accumulate(item_ls.begin(),item_ls.end(),0,value_accumulate); | |
int num_item = item_ls.size(); | |
int DP[num_item+1][max_val+1] = {}; | |
for(int v=1; v<=max_val;++v){ | |
DP[0][v] = INT_MAX; | |
} | |
for(int n=1;n<=num_item;++n){ | |
for(int v=1;v<=max_val;++v){ | |
if(v < item_ls[n-1].value) | |
DP[n][v] = std::min(item_ls[n-1].weight,DP[n-1][v]); | |
else{ | |
if(DP[n-1][v-item_ls[n-1].value] < INT_MAX) | |
DP[n][v] = std::min(DP[n-1][v],DP[n-1][v-item_ls[n-1].value]+item_ls[n-1].weight); | |
else | |
DP[n][v] = DP[n-1][v]; | |
} | |
} | |
} | |
std::vector<int> picked_items; picked_items.reserve(num_item); | |
int val; | |
int n = num_item; | |
for (int v = 1; v <= max_val; v++) { | |
if(DP[num_item][v] > cap){ // find the border | |
val = v-1; | |
break; | |
} | |
} | |
while(n>=1){ // backtrace the solution | |
if(DP[n][val] - DP[n-1][val-item_ls[n-1].value] == item_ls[n-1].weight){ | |
picked_items.push_back(n-1); | |
val -= item_ls[n-1].value; | |
} | |
n--; | |
} | |
return picked_items; | |
} | |
} | |
#endif |
/**************************************************************************** | |
FileName [ knapsack.h ] | |
Synopsis [ Knapsack Solver ] | |
Author [ Jui-Yang (Tony) Hsu] | |
Copyright [ Copyleft(c) 2017 NTUEE, Taiwan ] | |
****************************************************************************/ | |
#ifndef KNAPSACK_H | |
#define KNAPSACK_H | |
#include <vector> | |
#include <algorithm> | |
/** | |
* Note: weight type must be integer | |
**/ | |
namespace knapsack{ | |
struct Item{ | |
int value; | |
int weight; | |
Item(int value=0,int weight=0): value(value),weight(weight){} | |
}; | |
//Given the item list (item w/ weight and value) and capacity constraint | |
//Returns an integer vector representing the picked item's id of OPT soltion | |
//DP[n,w]: max value of picked item (1 ~ n) s.t weight <= w | |
std::vector<int> knapsack_solver(std::vector<Item>& item_ls, int cap){ | |
int num_item = item_ls.size(); | |
int DP[num_item+1][cap+1] = {}; | |
for(int n=1;n<=num_item;++n){ | |
for(int w=1;w<=cap;++w){ | |
if(w < item_ls[n-1].weight) | |
DP[n][w] = DP[n-1][w]; | |
else | |
DP[n][w] = std::max(DP[n-1][w],DP[n-1][w-item_ls[n-1].weight]+item_ls[n-1].value); | |
} | |
} | |
std::vector<int> picked_items; picked_items.reserve(num_item); | |
for(int n=num_item,w=cap;n>=1;--n){ | |
if(w-item_ls[n-1].weight >= 0 && (DP[n-1][w-item_ls[n-1].weight] + item_ls[n-1].value== DP[n][w])){ | |
picked_items.push_back(n-1); | |
w -= item_ls[n-1].weight; | |
} | |
} | |
return picked_items; | |
} | |
} | |
#endif |
視題目所給的測資範圍,選擇running time 較小的實做方式。