Advanced Algorithm - Knapsack Problem

Rounding Data & Dynamic Programming

背包問題為一個典型的最佳化問題,想像你來到了一個寶庫,裡頭有一些寶物,都有各自的價值和重量,但你只帶了一個背包(而且負重還有限制),要怎麼取寶物才能在背得走的前提下,帶走價值總和儘可能高的寶物們呢?

這裡我們考慮最基本的 0/1 - Knapsack Problem 。簡單的 formulation 如下:

We have nn items, each one with value viv_i and weight wiw_i. The weight limit of the backpack is WW.

Goal: Select set of items Ssuch thatSviis maximized subject to SwiW \text{Goal: Select set of items } S \, \text{such that} \sum_{S}v_i \, \text{is maximized subject to } \sum_{S}w_i \leq W

2-Approx Algorithm

若今天物品是可分割的話,有一個簡單的 greedy 方法可以找到最佳解 (依據viwi\frac{v_i}{w_i}作排序,選擇單位重量價值最高的物品,塞到背包塞不下為止)。而在 0/1 - Knapsack 中,也可利用此方法找到 2-Approx 的解。

Algorithm

Sort items according to viwi \frac{v_i}{w_i} in decending order. Choose items in that order until constraint DOESN'T satisfy, then pick max(v1+v2++vi,max(v_1 + v_2 + \cdots +v_i, vi+1v\scriptstyle{i+1}))

DP-based Algorithm

概念是給定欲達到的價值(vv),從物品的子集(item 1 ~ i)中,找到所需重量最小的物品集合之重量 ( A[i,v]A[i,v] )

Algorithm

Let A[i,v]A[i,v]: min. weight chosen from items 1 ~ i such that valuev\sum \text{value} \geq v

initial condition: A[0,v]=(v0,)?,0:A[0,v] = (v \leq 0,)?,0:\infty

recursive relation: A[i,v]=min(A[i1,v]i,is not picked,A[i1,vvi]+wii,is picked)A[i,v] = \min(\stackrel{\textcolor{blue}{i,\text{is not picked}}}{A[i-1,v]},\stackrel{\textcolor{blue}{i,\text{is picked}}}{A[i-1,v-v_i]+w_i})

Then search the n-th column (along the value axis),
we can find v;such that;A[n,v]WA[n,v+1]>Wv^{\star} ; \text{such that}; A[n,v^{\star}] \leq W \land A[n,v^{\star}+1] > W

The time complexity is Θ(n,v)\Theta(n , v^{\star})

然而,我們已知 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 \uparrow)

一般而言,我們會以二進位制儲存數字,所以 input size 應為 logvi+logwi\sum \log{v_i} + \sum \log{w_i},然而方才的演算法卻會一個一個地掃過 value axis ,直到找到解為止,最壞情況下(WW相當寬鬆),複雜度為 Θ(n,vi)\Theta(n , \sum{v_i})
\Rightarrow 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+ ϵ\epsilon)-Approx Algorithm

剛剛 DP 的方法壞就壞在必須線性掃過 value axis, 我們是否能過不要檢查每一個 vv ,而是跳著檢查某些 vv就好了呢?從上述的直覺,發展出 rounding 這個 scheme,犧牲一些精確度(1,+,ϵ1,+, \epsilon),將複雜度壓回 polynomial。

Algorithm

Let b=ϵ2nmaxivib = \frac{\epsilon}{2n}\max_i v_i

Now, consider the rounded instance 0-1 Knapsack\text{0-1 Knapsack}^{\prime} , wi^=wi,,W^=W,,vi^=vibb\hat{w_i} = w_i , , \hat{W} = W , , \textcolor{blue}{\hat{v_i} = \lceil \frac{v_i}{b} \rceil \cdotp b}

\Rightarrow Running time for solving 0-1 Knapsack\text{0-1 Knapsack}^{\prime} is Θ(nvb)=Θ(n3ϵ)\Theta(n \cdotp \frac{v^{\star}}{b}) = \Theta(\frac{n^3}{\epsilon})

vbvibnmaxivib=2n2ϵ \because \frac{v^{\star}}{b} \leq \frac{\sum v_i}{b} \leq \frac{n \cdotp \max_i v_i}{b} = \frac{2n^2}{\epsilon}

Correctness Check

value(OPT) in 0-1 Knapsack\
\leq value(OPT) in 0-1 Knapsack\text{0-1 Knapsack}^{\prime} ((\because 進位)\
value\leq \text{value} (OPT\text{OPT}^{\prime}) in 0-1 Knapsack\text{0-1 Knapsack}^{\prime} \
value\leq \text{value} (OPT\text{OPT}^{\prime}) in 0-1 Knapsack + nbnb (直接就加一 個刻度)\
= value(OPT\text{OPT}^{\prime}) + ϵ2maxivivalue(OPT)\frac{\epsilon}{2} \underset{\textcolor{blue}{\leq \text{value(OPT)}}}{\max_i v_i}

\Rightarrow value(OPT\text{OPT}^{\prime}) ,(1ϵ2)\geq , (1-\frac{\epsilon}{2}) value(OPT)\
\Rightarrow value(OPT\text{OPT}^{\prime}) ,(11+ϵ)\geq , (\frac{1}{1 + \epsilon}) value(OPT) ( 1ϵ211+ϵ\because \textcolor{red}{1-\frac{\epsilon}{2} \geq \frac{1}{1+\epsilon}} )

Improvement version

上題的 b=ϵ2nV~b = \frac{\epsilon}{2n}\tilde{V} 可以想成離散化 vv 值的刻度大小,我們可否在保持( 1+ϵ 1 + \epsilon ) - approx. ratio 的前提下,跳大格一點( bb 取大一點)。從上式的推導可以發現,若想保持 approx. ratio ,V~\tilde{V} 必須小於 value(OPT),而想降低 running 的 order , V~\tilde{V} 需要跟 vv^{\star} 差不多 order 。也就是我們需要估計一個比 value(OPT) 小,但不會差太多的 V~\tilde{V} , V~=Θ(v)\tilde{V} = \Theta(v^{\star})。 (而且估計的 running time 不能高於之後 derive 出來的複雜度)

Greedy Algorithm to estimate V~\tilde{V}

Pick  V~=max(v1+v2++vi,vi+1) \text{Pick} \; \tilde{V} = \max(v_1 + v_2 + \cdots +v_i, v\scriptstyle{i+1})

Poly-time Approximation Scheme (PTAS)

Given ϵ>0\epsilon > 0, can find (1+ϵ1 + \epsilon)-approx. algorithm w/ running time is poly. for constant 1ϵ\frac{1}{\epsilon}

Fully Poly-time Approximation Scheme (FPTAS)

Given ϵ>0\epsilon > 0, can find (1+ϵ1 + \epsilon)-approx. algorithm w/ running time is poly. for constant 1ϵ\frac{1}{\epsilon} and nn \
e.g 剛剛提到的 Rounded DP-based Algorithm

Psuedo-Poly Implementation

  • Θ(n,v)\Theta(n , v^{\star})
/****************************************************************************
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
view raw knapsack_2.h hosted with ❤ by GitHub
/****************************************************************************
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
view raw knapsack.h hosted with ❤ by GitHub

視題目所給的測資範圍,選擇running time 較小的實做方式。