演算法——貪心演算法解0-1揹包問題
問題的描述
我們先根據一個貪心演算法的經典應用例項,然後給出貪心演算法的實現步驟與關鍵環節,最後給出C++程式碼求解0-1揹包問題。
揹包問題(Knapsack Problem):有
N N件物品有一個承重(也可受限於體積)為C C的揹包,每件物品具有二維屬性,分別是重量屬性wi,i=1,…,N w_i,\quad i=1,\ldots,N,和價值屬性pi,i=1,…,N p_i,\quad i=1,\ldots,N,求解將哪幾件物品裝入揹包可使這些物品在重量不超過C C的情況下價值總和最大。揹包問題給我們提供了一個模型,由此我們可以求解貨箱裝載問題。這一問題隱含了一個條件,每個物品只有一件,也就是要麼不選取(UNCHOSEN,0),要麼選取(CHOSEN,1),因此又稱為0-1揹包問題。
貪心演算法的基本設計思想有以下三個步驟:
建立對問題精確描述的數學模型,包括定義最優解的模型。
顯然本題,就是重量不超過
C C條件下的價值最大
\max W(n)\\
s.t. \sum_iC_i\leq C
將問題分解為一系列子問題,同時定義子問題的最優解結構
貪心演算法的關鍵環節,將問題分解為後續的一個一個的子問題。本例而言,就是在(根據一定的標準)選定一個物品(重量為
wi w_i)的情況下,在餘下的物品中選擇下一個物品此時的問題的條件是重量不超過C′:=C−wi C':=C-w_i。
maxW(n):=wi+W(n−1)s.t.∑j∖iwj≤C−wi \max W(n):=w_i+W(n-1)\\ s.t. \sum_{j\setminus i}w_j\leq C-w_i引用相應的貪心原則(比如重量最輕,價值最高,價效比最高)確定每個子問題的區域性最優解(上文所說的
Ci C_i),並根據最優解的模型,用子問題的區域性最優解堆疊出全域性最優解。
下面我們來看這一具體的例項,
C=150 C=150, 最大承重wi=[35,30,60,50,40,10,25] w_i=[35, 30, 60, 50, 40, 10, 25],每個物品的重量pi=[10,40,30,50,35,40,10] p_i=[10, 40, 30, 50, 35, 40, 10],每個物品的價格
關鍵在於子問題的定義,本例,我麼可以將子問題定義為,在選取某一物品(
w_i
)放入揹包以後,在揹包容量還有C':=C-w_i
的情況下,選擇下一個物品放入揹包。
如何選擇每一次子問題的所需確定的物品呢?這正是貪心策略的選擇問題了。對於本題,常見的貪婪策略有三種,
根據物品價值選擇,每次都選價值最高的物品
[4, 2, 6, 5]-> 130(總重量),165(價值)
根據物品重量選擇,每次都選最輕的
[6, 7, 2, 1, 5] -> 140(總重量),155(總價格)
根據價值密度(也即價效比),每次都選價效比最高的
價效比:[0.286, 1.333, .5, 1., .0875, 4., 1.2]
[6, 2, 7, 4, 1] -> 150(總重量),170(總價格)
演算法的實現
演算法流程如下:
元素資料結構的定義
問題資料結構的定義
迴圈子問題,直到全部子問題都得以解決,退出迴圈
下面給出其實現:
enum STATUS
{
UNCHOSEN,
CHOSEN,
NOTALLOWED
};
// 物品的資料結構定義
typedef struct tagObject
{
int weight;
int price;
// double density;
// 這裡也可增加這樣一個冗餘性定義,用於第三種貪心規則
STATUS status;
}OBJ;
// 問題的資料結構定義
typedef struct tagKnapsackProblem
{
std::vector<OBJ> objs;
int total;
// std::vector<int> selected; // 貪心規則下得到的物品
// int weights; // 貪心規則下得到的總的物品重量
// int prices; // 貪心規則下得到的總的物品價格
}KnapsackProblem;
// 定義三種貪心演算法的函式指標
typedef int(*SELECT_POLICY)(std::vector<OBJ>&);
void greedyAlgo(KnapsackProblem* problem, SELECT_POLICY spFunc)
{
int idx;
int cur = 0;
while ((idx = spFunc(problem->objs)) != -1)
{
// idx標識可選的物品
if (problem->objs[idx].weight <= problem->total - cur)
{
std::cout << idx + 1 << std::endl;
// 列印每一個子問題的最優選擇
problem->objs[idx].status = CHOSEN;
cur += problem->objs[idx].weight;
}
else
{
problem->objs[idx].status = NOTALLOWED;
}
}
}
int chooseFunc1(std::vector<OBJ>& objs)
{
int idx = -1;
int tmp = 0;
for (size_t i = 0; i < objs.size(); ++i)
{
if (objs[i].status == UNCHOSEN && tmp < objs[i].price)
{
tmp = objs[i].price;
idx = i;
}
}
return idx;
}
int chooseFunc2(std::vector<OBJ>& objs)
{
int idx = -1;
int tmp = 1000000;
for(size_t i = 0; i < objs.size(); ++i)
{
if (objs[i].status == UNCHOSEN && tmp > objs[i].weight)
{
tmp = objs[i].weight;
idx = i;
}
}
return idx;
}
int chooseFunc3(std::vector<OBJ>& objs)
{
int idx = -1;
double tmp = 0.;
for(size_t i = 0; i < objs.size(); ++i)
{
if (objs[i].status == UNCHOSEN &&
tmp < static_cast<double>(objs[i].price)/objs[i].weight)
{
tmp = static_cast<double>(objs[i].price)/objs[i].weight;
idx = i;
}
}
return idx;
}
// 客戶端程式碼
int main(int, char**)
{
std::vector<OBJ> objs{{35, 10, UNCHOSEN}, {30, 40, UNCHOSEN}, {60, 30, UNCHOSEN}, {50, 50, UNCHOSEN},
{40, 35, UNCHOSEN}, {10, 40, UNCHOSEN}, {25, 10, UNCHOSEN}};
KnapsackProblem problem = {objs, 150};
// greedyAlgo(&problem, chooseFunc1);
// greedyAlgo(&problem, chooseFunc2);
greedyAlgo(&problem, chooseFunc3); // 三種貪心規則分別執行。
return 0;
}
0-1找零問題
有了上述0-1(to be or not to be, 只能二選一)揹包問題提供的模型,我們便可比較輕易的仿製上述設計與程式設計,解決0-1找零錢問題:
貨幣
mi=[25,10,5,1] m_i=[25, 10, 5, 1]分四種,需找給使用者41分錢。
enum STATUS
{
CHOSEN,
UNCHOSEN,
NOTALLOWED
};
typedef struct tagObject
{
int value;
STATUS status;
}OBJ;
typedef struct tagProblem
{
std::vector<OBJ> objs;
int total;
}Problem;
int findMax(std::vector<OBJ>& objs)
{
int idx = -1;
int tmp = 0;
for (size_t i = 0; i < objs.size(); ++i)
{
if (objs[i].status == UNCHOSEN && tmp < objs[i].value)
{
idx = i;
tmp = objs[i].value;
}
}
return idx;
}
void greedyAlgo(Problem* prob)
{
int idx;
int cur = 0;
while ((idx = findMax(prob->objs))!=-1)
{
if (prob->objs[idx].value <= prob->total - cur)
{
std::cout << idx + 1 << std::endl;
prob->objs[idx].status = CHOSEN;
cur += prob->objs[idx].value;
}
else
{
prob->objs[idx].status = NOTALLOWED;
}
}
}
int main(int, char**)
{
std::vector<OBJ> objs {{25, UNCHOSEN}, {10, UNCHOSEN}, {5, UNCHOSEN}, {1, UNCHOSEN}};
Problem prob = {objs, 41};
greedyAlgo(&prob);
return 0;
}
相關文章
- 【演算法】0-1揹包問題演算法
- 揹包問題演算法全解析:動態規劃和貪心演算法詳解演算法動態規劃
- leetcode題解(0-1揹包問題)LeetCode
- 整數0-1揹包問題
- 動態規劃解0-1揹包問題動態規劃
- 貪心演算法——換酒問題演算法
- 使用貪心演算法解決集合覆蓋問題演算法
- 貪心演算法篇——區間問題演算法
- 加油站問題(貪心演算法)演算法
- 動態規劃之 0-1 揹包問題詳解動態規劃
- 0-1揹包問題(動態規劃)動態規劃
- LeetCode解題記錄(貪心演算法)(二)LeetCode演算法
- LeetCode解題記錄(貪心演算法)(一)LeetCode演算法
- 活動選擇問題理解貪心演算法演算法
- 汽車加油問題 SDUT OJ 貪心演算法演算法
- 貪心演算法演算法
- 探索貪心演算法:解決最佳化問題的高效策略演算法
- [經典演算法]海盜分金問題sql求解(貪心演算法)演算法SQL
- 貪心演算法(貪婪演算法,greedy algorithm)演算法Go
- 01揹包問題理解動態規劃演算法動態規劃演算法
- 貪心演算法Dijkstra演算法
- 位元組面試演算法題-0,1揹包問題面試演算法
- 揹包問題(01揹包與完全揹包)
- 【動態規劃】0-1揹包問題原理和實現動態規劃
- 揹包問題的遞迴與非遞迴演算法遞迴演算法
- 常用演算法之貪心演算法演算法
- 學一下貪心演算法-學一下貪心演算法演算法
- Moving Tables(貪心演算法)演算法
- 9-貪心演算法演算法
- 揹包問題
- 【資料結構與演算法】揹包問題總結梳理資料結構演算法
- 揹包問題解題方法總結
- 01揹包問題的解決
- 價值密度優先貪心策略對分數揹包問題的正確性證明
- javascript演算法基礎之01揹包,完全揹包,多重揹包實現JavaScript演算法
- 演算法基礎–貪心策略演算法
- 01揹包問題
- 01 揹包問題
- 資料結構和演算法面試題系列—揹包問題總結資料結構演算法面試題