給定 n 種物品,第 i 種物品重量 w[i],價值 v[i];背包容量為 c。目標是選擇物品裝入背包,使總價值最大。根據物品是否可分割,分為兩類:
面對多維度屬性(價值、重量),貪心算法需確定單一決策指標:
| 度量標準 | 缺點 |
|---|---|
| 單純價值 v[i] | 高價值物品可能過重,導致裝入極少 |
| 單純重量 w[i] | 輕物品價值可能極低,浪費容量 |
| 單位價值 v[i]/w[i] | 綜合考量「性價比」,為最優指標 |
因此,貪心策略為:按單位價值由高到低排序,依序裝入。
#include <stdio.h>
#include <stdlib.h>
void package(int n, float c, float v[], float w[], float x[]);
void package0_1(int n, float c, float v[], float w[], float x[]);
int main(void) {
int n = 3;
float c = 20;
float v[] = {24, 15, 25};
float w[] = {15, 10, 18}; // 已按單位價值降序:1.6, 1.5, 1.388...
float *x = (float*)malloc(sizeof(float) * n);
printf("****** 分數背包 *******\n");
package(n, c, v, w, x);
printf("******* 0-1 背包 ******\n");
package0_1(n, c, v, w, x);
system("PAUSE");
free(x);
return 0;
}
/* 分數背包:可裝入部分物品 */
void package(int n, float c, float v[], float w[], float x[]) {
int i;
for (i = 0; i < n; i++) x[i] = 0.0;
for (i = 0; i < n; i++) {
if (w[i] > c) break; // 無法裝下整件
x[i] = 1.0; // 整件裝入
c -= w[i];
printf("放入第%d件物品,背包剩餘容量 %.2f\n", i+1, c);
}
if (i < n) { // 還有物品且背包未滿,裝入部分
x[i] = c / w[i];
printf("放入第%d件物品的 %.2f 部分,背包剩餘容量 0\n", i+1, x[i]);
}
}
/* 0-1 背包:只能整件裝入或不裝 */
void package0_1(int n, float c, float v[], float w[], float x[]) {
int i;
for (i = 0; i < n; i++) x[i] = 0.0;
for (i = 0; i < n; i++) {
if (w[i] > c) break; // 無法裝下整件,跳過
x[i] = 1.0;
c -= w[i];
printf("放入第%d件物品,背包剩餘容量 %.2f\n", i+1, c);
}
// 無法裝入部分物品,循環結束即為最終方案
}
分數背包具有貪心選擇性質:局部最優的單位價值選擇能推導出全域最優。證明核心在於「可分割性」消除了殘餘容量浪費,任何非貪心方案都可通過調整向貪心方案靠攏而不降低總價值。
0-1 背包中,物品不可分割導致殘餘容量可能無法被利用。貪心策略雖選了高性價比物品,但可能留下「裝不下任何剩餘物品」的碎片空間,導致總價值低於最優解。
容量 c=20,物品:
貪心解:選 C (w=15, v=24),剩餘 5 無法裝入 A 或 B → 總價值 24。
最優解:選 A + B (w=18+10=28>20 不行) → 實際最優為 B 兩件?但 0-1 只能各一件。重新構造:
容量 50。貪心選 1+2 (w=30, v=160),剩20裝不下3。最優解為 2+3 (w=50, v=220)。
| 問題類型 | 貪心策略 | 是否最優 | 最優解法 |
|---|---|---|---|
| 分數背包 | 按單位價值排序 | 是 | 貪心 O(n log n) |
| 0-1 背包 | 按單位價值排序 | 否(近似解) | 動態規劃 O(nc) |
這對經典對比深刻揭示了:問題結構(可分割性)決定了貪心算法的有效性。工程實踐中,若遇到 0-1 背包類問題,必須採用動態規劃、分支限界或近似算法,而非單純貪心。