graph TD
classDef root fill:#f8fafc,stroke:#64748b,stroke-width:2px,font-family:Inter;
classDef path fill:#eff6ff,stroke:#2563eb,stroke-width:2px,font-family:Inter;
classDef best fill:#ecfdf5,stroke:#059669,stroke-width:3px,font-family:Inter;
classDef overweight fill:#fef2f2,stroke:#ef4444,stroke-width:2px,stroke-dasharray:5 5,font-family:Inter;
R["根节点<br>UB=21.25"]:::root
A["装1<br>cw=2, cv=6"]:::path
B["装2<br>cw=7, cv=16"]:::path
C["装3<br>cw=11 > 10"]:::overweight
D["不装3<br>cw=7, cv=16"]:::path
E["装4<br>cw=10, cv=19<br>best=19"]:::best
R -->|先装1| A
A -->|再装2| B
B -->|尝试装3:超重| C
B -->|改为不装3| D
D -->|装4| E
DFS 先尝试“装1 -> 装2 -> 装3”。
由于 cw+w3=11>10,装入物品 3 会超重,因此转向“不装3”。
随后继续尝试“装4”,得到合法解 {1,2,4},更新 best=19。
第二张图展示 best=19 之后的回溯收尾。
此时只需要检查尚未展开的分支入口:如果入口节点的 UB 已经小于 best,整棵子树都可以跳过。
graph TD
classDef root fill:#f8fafc,stroke:#64748b,stroke-width:2px,font-family:Inter;
classDef done fill:#ecfdf5,stroke:#059669,stroke-width:2px,font-family:Inter;
classDef bound fill:#fff7ed,stroke:#f97316,stroke-width:2px,stroke-dasharray:5 5,font-family:Inter;
classDef leaf fill:#f8fafc,stroke:#94a3b8,stroke-width:1.5px,font-family:Inter;
R["已找到 best=19"]:::done
A["不装4<br>cv=16<br>叶子:不更新"]:::leaf
B["不装2<br>UB=16"]:::bound
C["不装1<br>UB=18"]:::bound
R -->|回溯到物品4| A
R -->|回溯到物品2| B
R -->|回溯到物品1| C
B -.->|UB < best,剪掉子树| B1["后续选择不再展开"]:::bound
C -.->|UB < best,剪掉子树| C1["后续选择不再展开"]:::bound
回溯过程如下:
“不装4”已经是叶子节点,当前价值 cv=16<best(19),不更新最优解。
“不装2”的状态为 (i=2,cw=2,cv=6)。
这里的 i 表示下一个要决策的物品下标,代码中使用 0-based 下标。从物品 3 起做分数贪心,UB=6+7+3=16<best(19),整棵子树剪掉。
“不装1”的状态为 (i=1,cw=0,cv=0)。
从物品 2 起做分数贪心,UB=10+7+1=18<best(19),整棵子树剪掉。
有了前两张图的铺垫,再回看整棵搜索树会更清楚。
下面这张图保留分支关系和关键结果。颜色含义如下:
蓝色:实际搜索路径。
绿色:更新最优解。
红色:超重剪枝。
橙色:限界剪枝。
灰色:因为祖先节点被剪枝而没有继续访问的子树。
graph TD
classDef root fill:#f8fafc,stroke:#64748b,stroke-width:2px,font-family:Inter;
classDef path fill:#eff6ff,stroke:#2563eb,stroke-width:2px,font-family:Inter;
classDef best fill:#ecfdf5,stroke:#059669,stroke-width:3px,font-family:Inter;
classDef overweight fill:#fef2f2,stroke:#ef4444,stroke-width:2px,stroke-dasharray:5 5,font-family:Inter;
classDef bound fill:#fff7ed,stroke:#f97316,stroke-width:2px,stroke-dasharray:5 5,font-family:Inter;
classDef leaf fill:#f8fafc,stroke:#94a3b8,stroke-width:1.5px,font-family:Inter;
classDef skipped fill:#f3f4f6,stroke:#d1d5db,stroke-width:1px,color:#9ca3af,font-family:Inter;
R["根<br>UB=21.25"]:::root
A["装1<br>cw=2, cv=6"]:::path
H["不装1<br>UB=18<br>剪枝"]:::bound
B["装2<br>cw=7, cv=16"]:::path
G["不装2<br>UB=16<br>剪枝"]:::bound
C["装3<br>超重"]:::overweight
D["不装3<br>cw=7, cv=16"]:::path
E["装4<br>cv=19<br>best=19"]:::best
F["不装4<br>cv=16<br>不更新"]:::leaf
G1["装3"]:::skipped
G2["不装3"]:::skipped
G3["装4"]:::skipped
G4["不装4"]:::skipped
H1["装2"]:::skipped
H2["不装2"]:::skipped
H3["装3"]:::skipped
H4["不装3"]:::skipped
R -->|装1| A
R -->|不装1| H
A -->|装2| B
A -->|不装2| G
B -->|装3| C
B -->|不装3| D
D -->|装4| E
D -->|不装4| F
G -.->|未访问| G1
G -.->|未访问| G2
G2 -.->|未访问| G3
G2 -.->|未访问| G4
H -.->|未访问| H1
H -.->|未访问| H2
H2 -.->|未访问| H3
H2 -.->|未访问| H4
graph TD
classDef current fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,font-family:Inter;
classDef prev fill:#f8fafc,stroke:#cbd5e1,stroke-width:1.5px,font-family:Inter;
classDef choose fill:#eff6ff,stroke:#3b82f6,stroke-width:1.5px,font-family:Inter;
classDef result fill:#ecfdf5,stroke:#10b981,stroke-width:2px,font-family:Inter;
A["当前格<br>f[2][7]"]:::current
B["不装物品2<br>看 f[1][7] = 6"]:::prev
C["装物品2<br>看 f[1][2] + 10 = 16"]:::choose
D["取最大值<br>f[2][7] = 16"]:::result
A --> B
A --> C
B --> D
C --> D
graph TD
classDef root fill:#f8fafc,stroke:#64748b,stroke-width:2px,font-family:Inter;
classDef active fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,font-family:Inter;
classDef wait fill:#eff6ff,stroke:#3b82f6,stroke-width:1.5px,font-family:Inter;
R["根节点<br>UB=21.25"]:::root
A["装1<br>UB=21.25<br>下一步扩展"]:::active
B["不装1<br>UB=18.00<br>暂存队列"]:::wait
R -->|装1| A
R -->|不装1| B
graph TD
classDef active fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,font-family:Inter;
classDef queue fill:#eff6ff,stroke:#3b82f6,stroke-width:1.5px,font-family:Inter;
classDef root fill:#f8fafc,stroke:#64748b,stroke-width:2px,font-family:Inter;
R["root<br>UB=21.25<br>出队扩展"]:::active
A["装1<br>UB=21.25<br>队首"]:::queue
B["不装1<br>UB=18.00<br>等待"]:::queue
R -->|装1| A
R -->|不装1| B
扩展 装1 后,装2 的上界仍为 21.25,继续排在队首;不装2 的上界为 16.00,此时 best 已更新为 16,所以它不会入队。
graph TD
classDef active fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,font-family:Inter;
classDef queue fill:#eff6ff,stroke:#3b82f6,stroke-width:1.5px,font-family:Inter;
classDef bound fill:#fff7ed,stroke:#f97316,stroke-width:2px,stroke-dasharray:5 5,font-family:Inter;
classDef wait fill:#f8fafc,stroke:#94a3b8,stroke-width:1.5px,font-family:Inter;
A["装1<br>UB=21.25<br>出队扩展"]:::active
B["装2<br>UB=21.25<br>队首"]:::queue
C["不装2<br>UB=16.00<br>不入队"]:::bound
D["不装1<br>UB=18.00<br>等待"]:::wait
A -->|装2| B
A -->|不装2| C
D -.->|仍在队列| B
graph TD
classDef active fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,font-family:Inter;
classDef queue fill:#eff6ff,stroke:#3b82f6,stroke-width:1.5px,font-family:Inter;
classDef overweight fill:#fef2f2,stroke:#ef4444,stroke-width:2px,stroke-dasharray:5 5,font-family:Inter;
classDef wait fill:#f8fafc,stroke:#94a3b8,stroke-width:1.5px,font-family:Inter;
A["装1,2<br>UB=21.25<br>出队扩展"]:::active
B["装3<br>cw=11>10<br>超重"]:::overweight
C["不装3<br>UB=19.00<br>队首"]:::queue
D["不装1<br>UB=18.00<br>等待"]:::wait
A -->|装3| B
A -->|不装3| C
D -.->|仍在队列| C
graph TD
classDef active fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,font-family:Inter;
classDef best fill:#ecfdf5,stroke:#10b981,stroke-width:3px,font-family:Inter;
classDef bound fill:#fff7ed,stroke:#f97316,stroke-width:2px,stroke-dasharray:5 5,font-family:Inter;
classDef wait fill:#f8fafc,stroke:#94a3b8,stroke-width:1.5px,font-family:Inter;
A["装1,2,不装3<br>UB=19.00<br>出队扩展"]:::active
B["装4<br>cv=19<br>best=19"]:::best
C["不装4<br>UB=16.00<br>不入队"]:::bound
D["不装1<br>UB=18.00<br>等待"]:::wait
A -->|装4| B
A -->|不装4| C
D -.->|仍在队列| B
graph TD
classDef best fill:#ecfdf5,stroke:#10b981,stroke-width:3px,font-family:Inter;
classDef bound fill:#fff7ed,stroke:#f97316,stroke-width:2px,stroke-dasharray:5 5,font-family:Inter;
A["当前 best=19"]:::best
B["不装1<br>UB=18.00<br>剪枝"]:::bound
A -->|UB <= best| B
把五个步骤合在一起,可以得到整棵搜索树。颜色含义如下:
黄色:按 UB 优先扩展的路径。
蓝色:曾进入优先队列等待的节点。
绿色:更新 best=19。
橙色:UB≤best,剪枝或不入队。
红色:超重剪枝。
graph TD
classDef root fill:#f8fafc,stroke:#64748b,stroke-width:2px,font-family:Inter;
classDef active fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,font-family:Inter;
classDef queue fill:#eff6ff,stroke:#3b82f6,stroke-width:1.5px,font-family:Inter;
classDef best fill:#ecfdf5,stroke:#10b981,stroke-width:3px,font-family:Inter;
classDef bound fill:#fff7ed,stroke:#f97316,stroke-width:2px,stroke-dasharray:5 5,font-family:Inter;
classDef overweight fill:#fef2f2,stroke:#ef4444,stroke-width:2px,stroke-dasharray:5 5,font-family:Inter;
R["根<br>UB=21.25"]:::root
A["装1<br>UB=21.25<br>第1个扩展"]:::active
H["不装1<br>UB=18.00<br>先等待,后剪枝"]:::queue
B["装2<br>UB=21.25<br>第2个扩展"]:::active
G["不装2<br>UB=16.00<br>不入队"]:::bound
C["装3<br>cw=11>10<br>超重"]:::overweight
D["不装3<br>UB=19.00<br>第3个扩展"]:::active
E["装4<br>cv=19<br>best=19"]:::best
F["不装4<br>UB=16.00<br>不入队"]:::bound
R -->|装1| A
R -->|不装1| H
A -->|装2| B
A -->|不装2| G
B -->|装3| C
B -->|不装3| D
D -->|装4| E
D -->|不装4| F
H -.->|最后出队<br>UB < best| Hx["剪枝"]:::bound
这意味着,容量 W 翻倍时,输入长度只增加 1 bit,但动态规划时间会直接翻倍。当 W=109 时,即使 n=10,动态规划也要处理 1010 个状态,可能比指数搜索还慢。
因此,动态规划只有在 W 的值被限制在 n 的多项式级别内时才是高效的。若 W 的值呈指数增长或为浮点数,通常就需要考虑分支限界、按价值维度动态规划,或者使用近似算法(如 FPTAS)来求取近似最优解。