[資料結構與演算法]-動態規劃之揹包演算法終極版
序:
工作快七個年頭了,現在寫起演算法來很是吃力,頭腦生鏽得很是厲害。可想而知,平時寫的程式碼質量是有多差。看來基本功得不斷打磨練習。
揹包題目:
有n個物品,每個物品具有重量和價值兩個屬性。現有一揹包最多能裝重量w,求出價值最大的物品選擇方案。
練習題目如圖:
用動態規劃思維分析
動態規劃核心三要素:最優子結構,邊界,狀態轉移公式。
最優子結構:每個物品有兩個狀態,被裝或不被裝。所以被分解程如下兩個子結構
邊界:當n為1時,若物品重量小於揹包w,則允許物品被裝。若物品重量大於揹包w,則不允許被裝。
狀態轉移公式(用i表示物品重量陣列):
f(n,w) = 0;(n<1)或(n==1且i[0] > w)
f(n,w) = i[0];(n==1且i[0] < w)
f(n,w) = f(n-1, w);(n>1且i[n-1] > w)
f(n,w)= max(f(n-1,w),f(n-1,w-i[n-1]) + i[n-1]));(n>1且i[n-1] < w)
遞迴求解
演算法思想:自頂向下,有重複計算。
求解過程如下圖:
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 揹包演算法
* 題目:有n個物品,每個物品具有重量和價值兩個屬性。現有一揹包最多能裝重量w,求出價值最大的物品選擇方案。
*/
public class KnapsackAlgorithm {
/**
* 物品類
*/
private static class Item {
// 重量
private int weight;
// 價值
private int value;
public Item(int weight, int value) {
this.weight = weight;
this.value = value;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
public static void main(String[] args) {
int knapsackSize = 8;
// 初始化4個物品
Item[] items = new Item[4];
items[0] = new Item(2, 3);
items[1] = new Item(3, 4);
items[2] = new Item(4, 5);
items[3] = new Item(6, 6);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
KnapsackAlgorithm knapsackAlgorithm = new KnapsackAlgorithm();
System.out.println("current time:" + simpleDateFormat.format(new Date()));
ResultNode resultNode = knapsackAlgorithm.getMostValuableWays_recursion(knapsackSize, items, items.length);
System.out.println("max value:" + resultNode.getSumValue());
System.out.println("current time:" + simpleDateFormat.format(new Date()));
}
private static class ResultNode {
private int index;
private int sumValue;
private int sumWeight;
private ResultNode left;
private ResultNode right;
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public int getSumValue() {
return sumValue;
}
public void setSumValue(int sumValue) {
this.sumValue = sumValue;
}
public ResultNode getLeft() {
return left;
}
public void setLeft(ResultNode left) {
this.left = left;
}
public ResultNode getRight() {
return right;
}
public void setRight(ResultNode right) {
this.right = right;
}
public int getSumWeight() {
return sumWeight;
}
public void setSumWeight(int sumWeight) {
this.sumWeight = sumWeight;
}
}
/**
* 遞迴求解
* @return
*/
private ResultNode getMostValuableWays_recursion(int knapsackSize, Item[] items, int n) {
if (n == 1) {
if (items[n-1].getWeight() <= knapsackSize) {
ResultNode resultNode = new ResultNode();
resultNode.setIndex(n-1);
resultNode.setLeft(null);
resultNode.setRight(null);
resultNode.setSumValue(items[n-1].getValue());
resultNode.setSumWeight(items[n-1].getWeight());
return resultNode;
} else {
return null;
}
} else if (n < 1) {
return null;
}
if (items[n-1].getWeight() > knapsackSize) {
return getMostValuableWays_recursion(knapsackSize, items, n-1);
} else {
ResultNode leftNode = getMostValuableWays_recursion(knapsackSize, items, n-1);
if (leftNode != null && leftNode.getSumWeight() > knapsackSize) {
leftNode = null;
}
ResultNode node = getMostValuableWays_recursion(knapsackSize - items[n-1].getWeight(), items, n-1);
ResultNode rightNode = new ResultNode();
rightNode.setIndex(n-1);
rightNode.setRight(node);
rightNode.setSumValue(node != null ? node.getSumValue() + items[n - 1].getValue() : items[n - 1].getValue());
rightNode.setSumWeight(node != null ? node.getSumWeight() + items[n - 1].getWeight() : items[n - 1].getWeight());
if (rightNode.getSumWeight() > knapsackSize) {
rightNode = null;
}
if (leftNode != null && rightNode != null) {
if (leftNode.getSumValue() > rightNode.getSumValue()) {
return leftNode;
} else if (leftNode.getSumValue() < rightNode.getSumValue()) {
return rightNode;
} else {
ResultNode resultNode = new ResultNode();
resultNode.setIndex(-1);
resultNode.setLeft(leftNode);
resultNode.setRight(rightNode);
resultNode.setSumValue(leftNode.getSumValue());
return resultNode;
}
} else if (leftNode != null){
return leftNode;
} else if (rightNode != null) {
return rightNode;
} else {
return null;
}
}
}
}
注意:getMostValuableWays_recursion中的條件分支正好與狀態轉移公式的條件對應。
備忘錄求解
演算法思想:自頂向下,無重複計算。
此演算法也是利用遞迴求解,與遞迴求解的區別在於運用了新的資料結構(比如map)來快取算過的值。
/**
* 備忘錄求解
*/
private ResultNode getMostValuableWays_memo(int knapsackSize, Item[] items, int n, KnapsackMap map) {
if (n == 1) {
if (items[n-1].getWeight() <= knapsackSize) {
ResultNode resultNode = new ResultNode();
resultNode.setIndex(n-1);
resultNode.setLeft(null);
resultNode.setRight(null);
resultNode.setSumValue(items[n-1].getValue());
resultNode.setSumWeight(items[n-1].getWeight());
return resultNode;
} else {
return null;
}
} else if (n < 1) {
return null;
}
if (items[n-1].getWeight() > knapsackSize) {
if (!map.containsKey(n-1, knapsackSize)) {
map.put(n-1, knapsackSize, getMostValuableWays_memo(knapsackSize, items, n-1, map));
}
return map.get(n-1, knapsackSize);
} else {
if (!map.containsKey(n-1, knapsackSize)) {
map.put(n-1, knapsackSize, getMostValuableWays_memo(knapsackSize, items, n-1, map));
}
ResultNode leftNode = map.get(n-1, knapsackSize);
if (leftNode != null && leftNode.getSumWeight() > knapsackSize) {
leftNode = null;
}
if (!map.containsKey(n-1, knapsackSize - items[n-1].getWeight())) {
map.put(n-1, knapsackSize - items[n-1].getWeight(), getMostValuableWays_memo(knapsackSize - items[n-1].getWeight(), items, n-1, map));
}
ResultNode node = map.get(n-1, knapsackSize - items[n-1].getWeight());
ResultNode rightNode = new ResultNode();
rightNode.setIndex(n-1);
rightNode.setRight(node);
rightNode.setSumValue(node != null ? node.getSumValue() + items[n - 1].getValue() : items[n - 1].getValue());
rightNode.setSumWeight(node != null ? node.getSumWeight() + items[n - 1].getWeight() : items[n - 1].getWeight());
if (rightNode.getSumWeight() > knapsackSize) {
rightNode = null;
}
if (leftNode != null && rightNode != null) {
if (leftNode.getSumValue() > rightNode.getSumValue()) {
return leftNode;
} else if (leftNode.getSumValue() < rightNode.getSumValue()) {
return rightNode;
} else {
ResultNode resultNode = new ResultNode();
resultNode.setIndex(-1);
resultNode.setLeft(leftNode);
resultNode.setRight(rightNode);
resultNode.setSumValue(leftNode.getSumValue());
return resultNode;
}
} else if (leftNode != null){
return leftNode;
} else if (rightNode != null) {
return rightNode;
} else {
return null;
}
}
}
private static class Knapsack {
private int num;
private int weidht;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public int getWeidht() {
return weidht;
}
public void setWeidht(int weidht) {
this.weidht = weidht;
}
@Override
public int hashCode() {
return 2;
}
@Override
public boolean equals(Object obj) {
Knapsack objLocal = (Knapsack)obj;
return num == objLocal.getNum() && weidht == objLocal.getWeidht();
}
}
private static class KnapsackMap extends HashMap<Knapsack, ResultNode> {
public boolean containsKey(int num, int knapsack) {
Knapsack knapsack1 = new Knapsack();
knapsack1.setNum(num);
knapsack1.setWeidht(knapsack);
return super.containsKey(knapsack1);
}
public ResultNode get(int num, int knapsack) {
Knapsack knapsack1 = new Knapsack();
knapsack1.setNum(num);
knapsack1.setWeidht(knapsack);
return super.get(knapsack1);
}
public ResultNode put(int num, int knapsack, ResultNode value) {
Knapsack knapsack1 = new Knapsack();
knapsack1.setNum(num);
knapsack1.setWeidht(knapsack);
return super.put(knapsack1, value);
}
}
動態規劃求解
演算法思想:採用由低向上的思維方式,即從1個物品開始求解,直到n個物品。
求解過程:
第一步:
1kg | 2kg | 3kg | 4kg | 5kg | 6kg | 7kg | 8kg |
---|---|---|---|---|---|---|---|
0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
說明:揹包能裝8千克,所以表格分成8列。行數代表物品的規模。
單元格值即為f(n,w),n為物品數。若n為1,就包含第一個物品(w:2kg, v3$);n為2,就包含第一個和第二個物品(w:2kg,v:3$和w:3kg,v:4$);以此類推。
第二步:
列1 | 列2 | 列3 | 列4 | 列5 | 列6 | 列7 | 列8 |
---|---|---|---|---|---|---|---|
0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
第三步:
列1 | 列2 | 列3 | 列4 | 列5 | 列6 | 列7 | 列8 |
---|---|---|---|---|---|---|---|
0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
0 | 3 | 4 | 5 | 5 | 8 | 9 | 9 |
第四步:
列1 | 列2 | 列3 | 列4 | 列5 | 列6 | 列7 | 列8 |
---|---|---|---|---|---|---|---|
0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
0 | 3 | 4 | 5 | 5 | 8 | 9 | 9 |
0 | 3 | 4 | 5 | 5 | 8 | 9 | 9 |
注意:f(4,8)=第4行8列單元格值。
/**
* 動態規劃求解
*/
private int getMostValuableWays_dynamicPrograming(int knapsackSize, Item[] items, int n) {
int[] preResult = null;
int[] result = new int[knapsackSize];
for(int i = 1; i <= n; i++) {
for(int w = 1; w <= knapsackSize; w++) {
if (i == 1) {
if (items[i-1].getWeight() > w) {
result[w-1] = 0;
} else {
result[w-1] = items[i-1].getValue();
}
} else {
if (w-items[i-1].getWeight()-1 >=0) {
if (preResult[w-1] > preResult[w-items[i-1].getWeight()-1] + items[i-1].getValue()) {
result[w-1] = preResult[w-1];
} else {
result[w-1] = preResult[w-items[i-1].getWeight()-1] + items[i-1].getValue();
}
} else if (w-items[i-1].getWeight() == 0) {
result[w-1] = items[i-1].getValue();
} else {
result[w-1] = 0;
}
}
}
preResult = Arrays.copyOf(result, 8);
}
return result[knapsackSize-1];
}
演算法時間複雜度和空間複雜度分析
遞迴:
時間複雜度:O(2^n),隨物品數改變。
空間複雜度:O(2^n)
備忘錄:
時間複雜度:O(2^n),隨物品數改變。
空間複雜度:O(2^n)
動態規劃:
時間複雜度:O(n^w),隨物品數和揹包重量改變。
空間複雜度:O(w)
相關文章
- 演算法-動態規劃-完全揹包演算法動態規劃
- 01揹包問題理解動態規劃演算法動態規劃演算法
- 前端與演算法-動態規劃之01揹包問題淺析與實現前端演算法動態規劃
- 【資料結構與演算法】揹包問題總結梳理資料結構演算法
- 【動態規劃】揹包問題動態規劃
- 揹包問題----動態規劃動態規劃
- 揹包問題演算法全解析:動態規劃和貪心演算法詳解演算法動態規劃
- 動態規劃之0,1揹包問題動態規劃
- 動態規劃系列之六01揹包問題動態規劃
- 動態規劃 01揹包問題動態規劃
- 【動態規劃】01揹包問題動態規劃
- 動態規劃--01揹包問題動態規劃
- 動態規劃篇——揹包問題動態規劃
- 動態規劃-01揹包問題動態規劃
- 【資料結構與演算法】三個經典案例帶你瞭解動態規劃資料結構演算法動態規劃
- 動態規劃-揹包01問題推理與實踐動態規劃
- 【動態規劃】01揹包問題【續】動態規劃
- 動態規劃入門——動態規劃與資料結構的結合,在樹上做DP動態規劃資料結構
- 動態規劃之 0-1 揹包問題詳解動態規劃
- 【演算法學習筆記】動態規劃與資料結構的結合,在樹上做DP演算法筆記動態規劃資料結構
- 0-1揹包問題(動態規劃)動態規劃
- 01揹包動態規劃空間優化動態規劃優化
- 演算法-動態規劃演算法動態規劃
- 演算法_動態規劃演算法動態規劃
- 動態規劃演算法動態規劃演算法
- 資料結構與演算法(java版)資料結構演算法Java
- 演算法筆記之動態規劃(4)演算法筆記動態規劃
- Leetcode 題解演算法之動態規劃LeetCode演算法動態規劃
- 動態規劃之 KMP 演算法詳解動態規劃KMP演算法
- 資料結構和演算法面試題系列—揹包問題總結資料結構演算法面試題
- 動態規劃演算法原理與實踐動態規劃演算法
- 多重揹包動態規劃及空間優化動態規劃優化
- 動態規劃解0-1揹包問題動態規劃
- 資料結構與演算法之美資料結構演算法
- 演算法與資料結構之集合演算法資料結構
- 資料結構與演算法之排序資料結構演算法排序
- 資料結構與演算法之美-王爭-極客時間資料結構演算法
- 前端演算法 - 動態規劃前端演算法動態規劃