first commit

This commit is contained in:
2025-12-18 16:00:22 +08:00
commit 785f306726
69 changed files with 33171 additions and 0 deletions

BIN
dynamic/knapsack Executable file

Binary file not shown.

159
dynamic/knapsack.cpp Normal file
View File

@@ -0,0 +1,159 @@
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
#include <chrono>
#include <random>
struct Item {
int weight; // 物品重量
int value; // 物品价值
int count; // 物品数量(用于多重背包问题)
};
// 全局操作计数器,用于性能分析
long long ops_count = 0;
// =================================================================
// 完全背包问题算法实现
// =================================================================
// 算法1朴素动态规划三层循环
int complete_knapsack_v1(const std::vector<Item>& items, int capacity) {
ops_count = 0; // 重置操作计数器
int n = items.size();
if (n == 0) return 0;
std::vector<std::vector<int>> dp(n + 1, std::vector<int>(capacity + 1, 0));
// 遍历每个物品
for (int i = 1; i <= n; ++i) {
int w = items[i - 1].weight;
int v = items[i - 1].value;
// 遍历每个容量
for (int j = 0; j <= capacity; ++j) {
dp[i][j] = dp[i-1][j]; // 不选择第i个物品
ops_count++;
// 尝试选择k个第i个物品
for (int k = 1; k * w <= j; ++k) {
ops_count++;
if (dp[i-1][j - k * w] + k * v > dp[i][j]) {
dp[i][j] = dp[i-1][j - k * w] + k * v;
}
}
}
}
return dp[n][capacity];
}
// 算法2优化的二维动态规划两层循环
int complete_knapsack_v2(const std::vector<Item>& items, int capacity) {
ops_count = 0; // 重置操作计数器
int n = items.size();
if (n == 0) return 0;
std::vector<std::vector<int>> dp(n + 1, std::vector<int>(capacity + 1, 0));
// 遍历每个物品
for (int i = 1; i <= n; ++i) {
int w = items[i - 1].weight;
int v = items[i - 1].value;
// 遍历每个容量
for (int j = 0; j <= capacity; ++j) {
ops_count++;
if (j < w) {
dp[i][j] = dp[i - 1][j]; // 容量不足不能选择第i个物品
} else {
// 选择不选第i个物品或选择第i个物品的最大值
dp[i][j] = std::max(dp[i - 1][j], dp[i][j - w] + v);
}
}
}
return dp[n][capacity];
}
// 算法3空间优化的动态规划一维数组
int complete_knapsack_v3(const std::vector<Item>& items, int capacity) {
ops_count = 0; // 重置操作计数器
std::vector<int> dp(capacity + 1, 0); // 一维DP数组
// 遍历每个物品
for (const auto& item : items) {
// 从物品重量开始遍历容量(完全背包)
for (int j = item.weight; j <= capacity; ++j) {
ops_count++;
dp[j] = std::max(dp[j], dp[j - item.weight] + item.value);
}
}
return dp[capacity];
}
// =================================================================
// 基准测试运行器
// =================================================================
void run_experiments(int min_n, int max_n, int step_n, int trials, int capacity) {
// 初始化随机数生成器
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> weight_dist(1, 40); // 重量范围1-40
std::uniform_int_distribution<> value_dist(1, 100); // 价值范围1-100
// 输出CSV格式的表头
std::cout << "n,algo,time_us,ops\n";
// 对不同物品数量进行测试
for (int n = min_n; n <= max_n; n += step_n) {
if (n==0) continue;
long long total_time_v1 = 0, total_ops_v1 = 0;
long long total_time_v2 = 0, total_ops_v2 = 0;
long long total_time_v3 = 0, total_ops_v3 = 0;
// 进行多次试验取平均值
for (int t = 0; t < trials; ++t) {
// 生成随机物品
std::vector<Item> items(n);
for (int i = 0; i < n; ++i) {
items[i] = {weight_dist(gen), value_dist(gen), 0};
}
// 测试算法1的运行时间
auto start_v1 = std::chrono::high_resolution_clock::now();
complete_knapsack_v1(items, capacity);
auto end_v1 = std::chrono::high_resolution_clock::now();
total_time_v1 += std::chrono::duration_cast<std::chrono::microseconds>(end_v1 - start_v1).count();
total_ops_v1 += ops_count;
// 测试算法2的运行时间
auto start_v2 = std::chrono::high_resolution_clock::now();
complete_knapsack_v2(items, capacity);
auto end_v2 = std::chrono::high_resolution_clock::now();
total_time_v2 += std::chrono::duration_cast<std::chrono::microseconds>(end_v2 - start_v2).count();
total_ops_v2 += ops_count;
// 测试算法3的运行时间
auto start_v3 = std::chrono::high_resolution_clock::now();
complete_knapsack_v3(items, capacity);
auto end_v3 = std::chrono::high_resolution_clock::now();
total_time_v3 += std::chrono::duration_cast<std::chrono::microseconds>(end_v3 - start_v3).count();
total_ops_v3 += ops_count;
}
// 输出每个算法的平均结果
std::cout << n << ",v1," << total_time_v1 / trials << "," << total_ops_v1 / trials << "\n";
std::cout << n << ",v2," << total_time_v2 / trials << "," << total_ops_v2 / trials << "\n";
std::cout << n << ",v3," << total_time_v3 / trials << "," << total_ops_v3 / trials << "\n";
}
}
int main(int argc, char* argv[]) {
// 实验参数:最小物品数、最大物品数、步长、每个物品数的试验次数、背包容量
// 为保持算法1的合理运行时间使用较小的容量和物品数量
int min_n = 5; // 最小物品数
int max_n = 25; // 最大物品数
int step_n = 5; // 物品数步长
int trials = 10; // 每个物品数的试验次数
int capacity = 100; // 背包容量
run_experiments(min_n, max_n, step_n, trials, capacity);
return 0;
}

166
dynamic/labtemplate.typ Normal file
View File

@@ -0,0 +1,166 @@
#let times = "Times LT Pro"
#let times = "Times New Roman"
#let song = (times, "Noto Serif CJK SC")
#let hei = (times, "Noto Sans CJK SC")
#let kai = (times, "Noto Serif CJK SC")
#let xbsong = (times, "Noto Serif CJK SC")
#let fsong = (times, "Noto Serif CJK SC")
#let code = (times, "JetBrains Mono")
#let nudtlabpaper(title: "",
author1: "",
id1: "",
advisor: "",
jobtitle: "",
lab: "",
date: "",
header_str: "",
minimal_cover: false,
body) = {
// Set the document's basic properties.
set document(author: author1, title: title)
set page(
margin: (left: 30mm, right: 30mm, top: 30mm, bottom: 30mm),
)
// If minimal_cover is requested, render an otherwise-empty first page
// that only displays the "实验时间" near the bottom center.
if minimal_cover {
v(158pt)
align(center)[
#block(text(weight: 700, size: 30pt, font: hei, tracking: 1pt, "2025秋 -《算法设计与分析》"))
]
align(center)[
#block(text(weight: 700, size: 24pt, font: song, tracking: 1pt, "动态规划算法分析实验报告"))
]
// Keep standard margins but push content down toward the bottom.
v(220pt)
align(center)[
#block(text(size: 14pt, font: song, tracking: 9pt, "实验时间"))
]
v(2pt)
align(center)[
#block(text(size: 16pt, font: song, date))
]
pagebreak()
} else {
// Title row.
v(158pt)
align(center)[
#block(text(weight: 700, size: 30pt, font: hei, tracking: 1pt, "2025秋 -《算法设计与分析》"))
]
align(center)[
#block(text(weight: 700, size: 24pt, font: song, tracking: 1pt, "动态规划算法分析实验报告"))
]
v(103pt)
pad(
left: 1em,
right: 1em,
grid(
// columns: (80pt, 1fr),
// rows: (17pt, auto),
// text(weight: 700, size: 16pt, font: song, "实验名称:"),
// align(center, text(weight: "regular", size: 16pt, font: song, title)),
// text(""),
// line(length: 100%)
)
// #block(text(weight: 700, 1.75em, title))
// underline(text(weight: 700, size: 16pt, font: song, title))
)
// Author information.
v(62.5pt)
grid(
columns: (0.25fr, 0.25fr, 0.25fr, 0.25fr),
rows: (20pt, 8pt, 20pt, 8pt, 20pt, 8pt, 20pt, 12pt),
text(size: 14pt, font: song, tracking: 9pt, "学员姓名"),
align(center, text(size: 14pt, font: song, author1)),
text(size: 14pt, font: song, tracking: 54pt, "学号"),
align(center, text(size: 14pt, font: times, id1)),
text(""),
line(length: 100%),
text(""),
line(length: 100%),
text(size: 14pt, font: song, tracking: 9pt, "指导教员"),
align(center, text(size: 14pt, font: song, advisor)),
text(size: 14pt, font: song, tracking: 54pt, "职称"),
align(center, text(size: 14pt, font: song, jobtitle)),
text(""),
line(length: 100%),
text(""),
line(length: 100%),
text(size: 14pt, font: song, tracking: 9pt, "实验室"),
align(center, text(size: 14pt, font: song, lab)),
text(size: 14pt, font: song, tracking: 9pt, "实验时间"),
align(center, text(size: 14pt, font: song, date)),
text(""),
line(length: 100%),
text(""),
line(length: 100%),
)
v(50.5pt)
align(center, text(font: hei, size: 15pt, "国防科技大学教育训练部制"))
pagebreak()
}
set page(
margin: (left: 30mm, right: 30mm, top: 30mm, bottom: 30mm),
numbering: "i",
number-align: center,
)
v(14pt)
align(center)[
#block(text(font: hei, size: 14pt, "《本科实验报告》填写说明"))
]
v(14pt)
text("")
par(first-line-indent: 2em, text(font: song, size: 12pt, "实验报告内容编排应符合以下要求:"))
par(first-line-indent: 2em, text(font: fsong, size: 12pt, "1采用A421cm×29.7cm白色复印纸单面黑字。上下左右各侧的页边距均为3cm缺省文档网格字号为小4号中文为宋体英文和阿拉伯数字为Times New Roman每页30行每行36字页脚距边界为2.5cm页码置于页脚、居中采用小5号阿拉伯数字从1开始连续编排封面不编页码。"))
par(first-line-indent: 2em, text(font: fsong, size: 12pt, "2报告正文最多可设四级标题字体均为黑体第一级标题字号为4号其余各级标题为小4号标题序号第一级用“一、”、“二、”……第二级用“”、“” ……第三级用“1.”、“2.” ……第四级用“1”、“2” ……,分别按序连续编排。"))
par(first-line-indent: 2em, text(font: fsong, size: 12pt, "3正文插图、表格中的文字字号均为5号。"))
pagebreak()
set page(
margin: (left: 30mm, right: 30mm, top: 30mm, bottom: 30mm),
numbering: "1",
number-align: center,
)
set heading(numbering: "1.1")
// set text(font: hei, lang: "zh")
show heading: it => box(width: 100%)[
#v(0.50em)
#set text(font: hei)
#counter(heading).display()
// #h(0.5em)
#it.body
]
// Main body.
set par(justify: true)
body
}
#let para(t) = par(first-line-indent: 2em, text(font: song, size: 10.5pt, t))
#let subpara(t) = par(first-line-indent: 2em, text(font: song, size: 10pt, t))
#let cb(t) = block(
text(font: ("Consolas","FangSong_GB2312"), t),
fill: luma(240),
inset: 1pt,
radius: 4pt,
// width: 100%,
)

9892
dynamic/main.pdf Normal file

File diff suppressed because one or more lines are too long

281
dynamic/main.typ Normal file
View File

@@ -0,0 +1,281 @@
#import "labtemplate.typ": *
#show: nudtlabpaper.with(
author1: "程景愉",
id1: "202302723005",
advisor: " 胡罡",
jobtitle: "教授",
lab: "306-707",
date: "2025.11.28",
header_str: "动态规划算法分析实验报告",
minimal_cover: true,
)
#set page(header: [
#set par(spacing: 6pt)
#align(center)[#text(size: 11pt)[《算法设计与分析》实验报告]]
#v(-0.3em)
#line(length: 100%, stroke: (thickness: 1pt))
],)
#show heading: it => box(width: 100%)[
#v(0.50em)
#set text(font: hei)
#it.body
]
#outline(title: "目录",depth: 3, indent: 1em)
// #pagebreak()
#outline(
title: [图目录],
target: figure.where(kind: image),
)
#show heading: it => box(width: 100%)[
#v(0.50em)
#set text(font: hei)
#counter(heading).display()
#it.body
]
#set enum(indent: 0.5em,body-indent: 0.5em,)
#pagebreak()
= 实验介绍
#para[
动态规划Dynamic Programming, DP是一种通过把原问题分解为相对简单的子问题的方式来求解复杂问题的方法。它常用于优化问题其中问题的最优解可以通过子问题的最优解来构造。本实验旨在深入理解动态规划算法在解决背包问题中的应用特别是完全背包问题及其优化并通过实验数据分析不同实现方式的性能差异。
]
= 实验内容
#para[
本实验主要围绕动态规划算法解决完全背包问题展开,并涉及多重背包问题的初步分析。具体内容包括:
]
+ 实现两种基于不同递推公式的完全背包动态规划算法。,
+ 对所实现的算法进行插桩,记录关键操作次数。,
+ 以物品种类数量 $n$ 为输入规模,通过大量随机测试样本,统计不同算法的平均运行时间与关键操作次数。,
+ 改变物品种类规模 $n$,对比分析不同规模下各算法的性能,并利用 Python 绘制数据图。,
+ 实现完全背包问题的一维数组空间优化版本,并与上述算法进行对比。,
+ (附加)对多重背包问题实现至少两种动态规划算法,并进行性能分析。,
= 实验要求
#para[
运用动态规划算法求解完全背包问题并进行分析,具体要求如下:
]
+ 针对完全背包问题,实现基于两种递推公式的动态规划算法。
+ 在代码中插桩,记录关键操作次数(如查表次数等)。
+ 以物品种类的大小n为输入规模固定n随机产生大量测试样本统计两种算法的平均运行时间和关键操作次数并进行记录。
+ 改变物品种类规模对不同规模问题各算法的结果对比分析通过统计python画图插入到报告中记录与理论值进行对照分析。
+ 使用一维数组的方式解决整数背包问题,并记录其平均运行时间和关键操作次数,与上述两种算法进行对比。
#para[
附加:运用动态规划算法求解多重背包问题并进行分析,具体要求如下:
]
+ 多重背包即每种物品的数量有限第i种物品的数量上限为ki个
+ 对多重背包问题实现两种以上动态规划算法,并对其性能进行分析。
= 实验步骤
== 算法设计
=== 完全背包算法一:朴素三重循环动态规划
#para[
该算法是完全背包问题的一种直观解法,其递推关系考虑了对每个物品 $i$,我们可以选择不取,或者取 $k$ 件,其中 $k$ 可以是 1 到容量允许的最大值。
$"dp"[i][j]$ 表示在前 $i$ 种物品中选择,背包容量为 $j$ 时的最大价值。
递推公式为:
#box(fill: luma(240), radius: 3pt, inset: 8pt)[
#set text(size: 0.9em)
#align(center)[
$ "dp"[i][j] = "max"("dp"[i-1][j], "max"_(k=1)^(j/w_i)("dp"[i-1][j - k dot w_i] + k dot v_i)) $
]
]
其中 $w_i$ $v_i$ 分别表示第 $i$ 种物品的重量和价值。
该算法的时间复杂度为 $O(n dot W dot (W/w_"min"))$,其中 $n$ 为物品种类数,$W$ 为背包容量,$w_"min"$ 为物品的最小重量。
]
```cpp
int complete_knapsack_v1(const std::vector<Item>& items, int capacity) {
ops_count = 0;
int n = items.size();
if (n == 0) return 0;
std::vector<std::vector<int>> dp(n + 1, std::vector<int>(capacity + 1, 0));
for (int i = 1; i <= n; ++i) {
int w = items[i - 1].weight;
int v = items[i - 1].value;
for (int j = 0; j <= capacity; ++j) {
dp[i][j] = dp[i-1][j]; // Option to not take item i
ops_count++;
for (int k = 1; k * w <= j; ++k) {
ops_count++;
if (dp[i-1][j - k * w] + k * v > dp[i][j]) {
dp[i][j] = dp[i-1][j - k * w] + k * v;
}
}
}
}
return dp[n][capacity];
}
```
#align(center)[_代码 1: 完全背包算法一 C++ 实现_]
=== 完全背包算法二:优化二维动态规划
#para[
该算法是完全背包问题更常用且更高效的二维动态规划解法。它利用了完全背包的特性:在考虑第 $i$ 种物品时,如果选择放入该物品,那么接下来的决策仍然可以在包含第 $i$ 种物品的集合中进行。
递推公式为:
#box(fill: luma(240), radius: 3pt, inset: 8pt)[
#set text(size: 0.9em)
#align(center)[
$ "dp"[i][j] = "max"("dp"[i-1][j], "dp"[i][j - w_i] + v_i) $
]
]
其中 $"dp"[i-1][j]$ 表示不选择第 $i$ 种物品的最大价值,而 $"dp"[i][j - w_i] + v_i$ 表示选择至少一件第 $i$ 种物品,并在剩余容量 $j - w_i$ 中继续考虑第 $i$ 种物品(以及之前的物品)。
该算法的时间复杂度为 $O(n dot W)$,空间复杂度为 $O(n dot W)$
]
```cpp
int complete_knapsack_v2(const std::vector<Item>& items, int capacity) {
ops_count = 0;
int n = items.size();
if (n == 0) return 0;
std::vector<std::vector<int>> dp(n + 1, std::vector<int>(capacity + 1, 0));
for (int i = 1; i <= n; ++i) {
int w = items[i - 1].weight;
int v = items[i - 1].value;
for (int j = 0; j <= capacity; ++j) {
ops_count++;
if (j < w) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = std::max(dp[i - 1][j], dp[i][j - w] + v);
}
}
}
return dp[n][capacity];
}
```
#align(center)[_代码 2: 完全背包算法二 C++ 实现_]
=== 完全背包算法三:空间优化一维动态规划
#para[
该算法是对算法二的空间优化版本,它将二维 $"dp"$ 数组优化为一维 $"dp"$ 数组。由于计算 $"dp"[i][j]$ 时只依赖于 $"dp"[i-1]$ $"dp"[i]$ 自身(通过 $"dp"[j-w_i]$),因此可以通过在一维数组上正序遍历容量来实现。
递推公式为:
#box(fill: luma(240), radius: 3pt, inset: 8pt)[
#set text(size: 0.9em)
#align(center)[
$ "dp"[j] = "max"("dp"[j], "dp"[j - w_i] + v_i) $
]
]
该算法的时间复杂度仍为 $O(n dot W)$,但空间复杂度优化为 $O(W)$,极大地节省了内存。
]
```cpp
int complete_knapsack_v3(const std::vector<Item>& items, int capacity) {
ops_count = 0;
std::vector<int> dp(capacity + 1, 0);
for (const auto& item : items) {
for (int j = item.weight; j <= capacity; ++j) {
ops_count++;
dp[j] = std::max(dp[j], dp[j - item.weight] + item.value);
}
}
return dp[capacity];
}
```
#align(center)[_代码 3: 完全背包算法三 C++ 实现_]
== 实验环境与参数设置
#para[
本实验在 Linux 操作系统环境下进行C++ 代码使用 #link("https://gcc.gnu.org/")[GCC] 编译器 (`g++`) 进行编译,并以 (`-O2`) 级别进行优化。数据分析与绘图使用 #link("https://www.python.org/")[Python] 编程语言,依赖 #link("https://pandas.pydata.org/")[pandas]#link("https://matplotlib.org/")[matplotlib] #link("https://seaborn.pydata.org/")[seaborn] 等库。
]
#para[
实验中,我们固定背包容量 $W=100$,并随机生成物品。物品的重量在 $[1, 40]$ 范围内均匀分布,价值在 $[1, 100]$ 范围内均匀分布。为了消除随机性带来的误差,每个 $n$ 值(物品种类数)进行 $10$ 次独立实验,并取其平均运行时间及关键操作次数。物品种类数 $n$ $5$ 递增到 $25$,步长为 $5$
]
#para[
我们定义“关键操作次数”为动态规划表中状态值的更新或访问次数。具体在 C++ 代码中,通过全局变量 (`ops_count`) 在每次 (`dp`) 数组赋值或比较时进行累加。
]
== 数据收集与可视化
#para[
实验数据由 C++ 程序 (`knapsack`) 收集。该程序在每次运行完一个算法后,将物品种类数 $n$、算法名称v1、v2、v3、平均运行时间微秒和平均关键操作次数输出到标准输出并重定向保存至 (`results.csv`) 文件。
]
#para[
Python 脚本 (`plotter.py`) 负责读取 (`results.csv`) 文件,使用 (`matplotlib`) (`seaborn`) 库生成两幅图表:
]
+ 平均运行时间与物品种类数 $n$ 的关系图。
+ 平均关键操作次数与物品种类数 $n$ 的关系图,其中关键操作次数曲线采用对数坐标显示以更好地展现数量级差异。
#para[
这些图表将直观地展示不同算法的性能随问题规模变化的趋势。
]
= 实验结果
#para[
本节展示了不同动态规划算法在解决完全背包问题时,其平均运行时间与关键操作次数随物品种类数 $n$ 变化的实验结果。
]
#figure(
image("time_vs_n.png", width: 80%),
caption: [平均运行时间与物品种类数的关系],
)
#figure(
image("ops_vs_n.png", width: 80%),
caption: [平均关键操作次数与物品种类数的关系],
)
#para[
从上述图表中,我们可以观察到以下趋势:
]
- *算法一 (Naive DP)*:无论是在运行时间还是关键操作次数上,算法一都显著高于算法二和算法三。其增长趋势与其理论分析的 $O(n dot W dot (W/w_min))$ 复杂度吻合,表明该方法在实际应用中效率极低,尤其是在问题规模稍大时。
- *算法二 (Optimized 2D DP)*:算法二的运行时间和关键操作次数都呈现出与 $n$ 线性相关的增长趋势,这与其理论时间复杂度 $O(n dot W)$ 一致。与算法一相比,其性能有了大幅提升。
- *算法三 (Space-Optimized 1D DP)*:算法三在运行时间上与算法二表现相似,同样呈现出与 $n$ 线性相关的增长。在关键操作次数上,它也与算法二保持一致的增长模式。这验证了空间优化版本在不改变时间复杂度的前提下,能有效降低空间消耗。虽然理论上时间复杂度相同,但由于内存访问模式的改变(更少的内存分配,更好的缓存局部性),在某些情况下可能会有细微的性能提升,但在本实验的数据规模下,这种差异不明显。
#para[
总体而言,算法二和算法三在处理完全背包问题上表现出良好的可伸缩性,而算法三更是在空间效率上具有优势。算法一作为一种直观但效率低下的实现,仅适合理解概念,不适用于实际大规模问题。
]
= 实验总结
#para[
本实验通过实现和比较三种基于动态规划的完全背包算法,深入分析了不同递推关系和优化策略对算法性能的影响。实验结果清晰地表明,算法一(朴素三重循环)由于其较高的复杂性,在运行时间与关键操作次数上均表现出最差的性能,验证了其不适用于实际应用。
]
#para[
相比之下,算法二(优化二维动态规划)和算法三(空间优化一维动态规划)均展示出优越的性能,其时间复杂度为 $O(n dot W)$,运行时间随问题规模 $n$ 呈线性增长。特别是算法三,在保持与算法二相同时间复杂度的同时,将空间复杂度优化至 $O(W)$,这在处理大容量背包问题时具有显著优势。
]
#para[
本次实验不仅加深了对动态规划解决完全背包问题的理解,也强调了算法设计中选择合适的递推关系和进行空间优化的重要性。未来工作可以扩展到更复杂的背包问题,例如多重背包的更高效实现(如二进制优化)及其在更大规模数据下的性能分析。
]
#pagebreak()
= 附加:多重背包问题分析
== 多重背包算法一:朴素动态规划
#para[
多重背包问题与完全背包问题类似,但每种物品的数量是有限的。对于第 $i$ 种物品,其数量上限为 $k_i$ 个。
$"dp"[i][j]$ 表示在前 $i$ 种物品中选择,背包容量为 $j$ 时的最大价值。
递推公式为:
#box(fill: luma(240), radius: 3pt, inset: 8pt)[
#set text(size: 0.9em)
#align(center)[
$ "dp"[i][j] = "max"_(0 <= c <= "min"(k_i, j/w_i))("dp"[i-1][j - c dot w_i] + c dot v_i) $
]
]
其中 $w_i$$v_i$$k_i$ 分别表示第 $i$ 种物品的重量、价值和数量上限,$c$ 表示选择第 $i$ 种物品的件数。
该算法的时间复杂度为 $O(W dot "sum" k_i)$,在最坏情况下,如果 $k_i$ 很大,其性能会接近完全背包的朴素解法。若 $"sum" k_i$ 可以简化为 $K_"max"$,则复杂度为 $O(n dot W dot K_"max")$
]
```cpp
// Algorithm for Multiple Knapsack (Direct DP)
int multiple_knapsack_v1(const std::vector<Item>& items, int capacity) {
int n = items.size();
if (n == 0) return 0;
std::vector<std::vector<int>> dp(n + 1, std::vector<int>(capacity + 1, 0));
for (int i = 1; i <= n; ++i) {
int w = items[i - 1].weight;
int v = items[i - 1].value;
int k = items[i - 1].count; // Max count for this item
for (int j = 0; j <= capacity; ++j) {
dp[i][j] = dp[i-1][j];
for (int c = 1; c <= k && c * w <= j; ++c) {
dp[i][j] = std::max(dp[i][j], dp[i - 1][j - c * w] + c * v);
}
}
}
return dp[n][capacity];
}
```
#align(center)[_代码 4: 多重背包算法一 C++ 实现_]
== 多重背包算法二:二进制优化
#para[
二进制优化是解决多重背包问题的一种高效方法。其核心思想是将每种数量有限的物品拆分成若干件特殊的“物品”,使得这些特殊物品的组合可以表示原物品的任意数量。具体来说,对于第 $i$ 种物品,如果其数量上限为 $k_i$,我们可以将其拆分为重量和价值分别为 $c dot w_i$ $c dot v_i$ 的“物品”,其中 $c$ $1, 2, 4, "dots", 2^p$,以及剩余的 $k_i - (2^(p+1)-1)$。这些 $c$ 的和可以表示从 $1$ $k_i$ 之间的任何一个整数。
]
#para[
拆分后,多重背包问题就转化为了一个 0/1 背包问题。我们可以使用 0/1 背包问题的标准动态规划方法(如与完全背包算法三类似的一维 DP 优化)来解决。
转化后的物品总数将从 $"sum" k_i$ 减少到 $"sum" "log" k_i$,从而将时间复杂度优化为 $O(W dot "sum" "log" k_i)$
]

BIN
dynamic/ops_vs_n.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

56
dynamic/plotter.py Normal file
View File

@@ -0,0 +1,56 @@
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import sys
def create_plots(csv_file_path):
"""
读取CSV文件生成图表并保存到磁盘
"""
try:
df = pd.read_csv(csv_file_path)
except FileNotFoundError:
print(f"错误:找不到文件 {csv_file_path}")
sys.exit(1)
# 算法名称映射
algo_names = {
'v1': '朴素动态规划(三层循环)',
'v2': '优化二维动态规划',
'v3': '空间优化一维动态规划'
}
df['algo_name'] = df['algo'].map(algo_names)
# 设置绘图风格
sns.set_theme(style="whitegrid")
# 图表1运行时间与物品数量的关系
plt.figure(figsize=(10, 6))
time_plot = sns.lineplot(data=df, x='n', y='time_us', hue='algo_name', marker='o', palette='viridis')
plt.title('平均运行时间与物品种类数量(n)的关系')
plt.xlabel('物品种类数量(n)')
plt.ylabel('平均运行时间(微秒)')
time_plot.legend(title='算法')
plt.grid(True, which='both', linestyle='--')
plt.savefig('time_vs_n.png', dpi=300)
plt.close()
# 图表2操作次数与物品数量的关系
plt.figure(figsize=(10, 6))
ops_plot = sns.lineplot(data=df, x='n', y='ops', hue='algo_name', marker='o', palette='plasma')
plt.title('关键操作次数与物品种类数量(n)的关系')
plt.xlabel('物品种类数量(n)')
plt.ylabel('平均关键操作次数(对数尺度)')
plt.yscale('log')
ops_plot.legend(title='算法')
plt.grid(True, which='both', linestyle='--')
plt.savefig('ops_vs_n.png', dpi=300)
plt.close()
if __name__ == '__main__':
if len(sys.argv) > 1:
# 使用命令行参数指定的CSV文件
create_plots(sys.argv[1])
else:
# 如果没有提供参数,使用默认文件名
create_plots('results.csv')

16
dynamic/results.csv Normal file
View File

@@ -0,0 +1,16 @@
n,algo,time_us,ops
5,v1,2,3011
5,v2,0,505
5,v3,0,399
10,v1,3,5641
10,v2,1,1010
10,v3,0,795
15,v1,7,8723
15,v2,1,1515
15,v3,0,1231
20,v1,9,14664
20,v2,1,2020
20,v3,0,1641
25,v1,9,14808
25,v2,2,2525
25,v3,0,2007
1 n algo time_us ops
2 5 v1 2 3011
3 5 v2 0 505
4 5 v3 0 399
5 10 v1 3 5641
6 10 v2 1 1010
7 10 v3 0 795
8 15 v1 7 8723
9 15 v2 1 1515
10 15 v3 0 1231
11 20 v1 9 14664
12 20 v2 1 2020
13 20 v3 0 1641
14 25 v1 9 14808
15 25 v2 2 2525
16 25 v3 0 2007

12
dynamic/task.txt Normal file
View File

@@ -0,0 +1,12 @@
运用动态规划算法求解完全背包问题并进行分析,具体要求如下:
针对完全背包问题,实现基于两种递推公式的动态规划算法;
在代码中插桩,记录关键操作次数(如查表次数等);
以物品种类的大小n为输入规模固定n随机产生大量测试样本统计两种算法的平均运行时间和关键操作次数并进行记录
改变物品种类规模对不同规模问题各算法的结果对比分析通过统计python画图插入到报告中记录与理论值进行对照分析
使用一维数组的方式解决整数背包问题,并记录其平均运行时间和关键操作次数,与上述两种算法进行对比。
附加:运用动态规划算法求解多重背包问题并进行分析,具体要求如下:
多重背包即每种物品的数量有限第i种物品的数量上限为ki个
对多重背包问题实现两种以上动态规划算法,并对其性能进行分析。

BIN
dynamic/time_vs_n.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.