贪心算法之分数背包问题
一、问题本质与核心区别
1. 问题定义
分数背包问题是经典的资源优化类问题,核心要素与目标如下:
- 输入条件:给定一组物品(每个物品含「重量」和「价值」两个属性)、一个固定容量的背包
- 核心规则:允许将物品分割成任意比例装入背包,无需完整装入
- 优化目标:在不超过背包容量的前提下,最大化背包内物品的总价值
2. 与 0-1 背包问题的关键差异
对比维度 | 分数背包问题 | 0-1 背包问题 |
物品装入规则 | 可分割,支持部分装入 | 不可分割,仅能完整装入或不装 |
适用算法 | 贪心算法(可获最优解) | 动态规划(贪心算法无效) |
决策逻辑复杂度 | 仅需按单位价值优先级选择 | 需考虑物品组合的整体价值 |
二、算法核心:贪心策略的原理与合理性
1. 贪心策略的核心逻辑
解决分数背包问题的贪心策略可概括为:优先选择「单位价值最高」的物品,依次装入背包(能完整装则装,若装不下则装入部分)。
其中,单位价值 的计算方式为:单位价值(ratio)= 物品价值(value)/ 物品重量(weight),代表每单位重量物品能带来的价值回报。
2. 策略合理性证明
该策略能确保得到最优解,核心原因有两点:
- 单位价值越高的物品,“重量性价比” 越高,优先装入可在有限容量内快速积累高价值。
- 由于允许物品分割,当遇到无法完整装入的高单位价值物品时,可装入剩余容量对应的比例,避免容量浪费,实现 “物尽其用”,最终总价值达到最大。
三、数据结构设计与关键变量
1. 核心数据结构:物品结构体(Item)
通过结构体封装物品的三个关键属性,便于统一存储和处理:
struct Item {int weight; // 物品重量int value; // 物品价值double ratio; // 单位价值
};
2. 关键变量说明
- items[M]:存储所有物品的数组,M为预设的最大容量
- n:用户输入的实际物品数量。
- capacity:用户输入的背包最大容量。
- totalValue:累加存储背包内物品的总价值。
- remaining:记录背包剩余容量。
四、核心算法实现(代码解析)
1. 第一步:物品排序函数(cmp)
需先对所有物品按「单位价值(ratio)」降序排序,为后续贪心选择铺路,排序函数作为 sort函数的比较器使用:
// 按单位价值降序排序,单位价值高的物品排在前面
bool cmp(Item a, Item b) {return a.ratio > b.ratio;
}
- 排序后,物品列表的优先级顺序为:单位价值从高到低,确保后续遍历能优先处理高价值密度的物品。
2. 第二步:贪心选择算法(maxValue 函数)
该函数是核心计算模块,实现物品的选择与价值累加,具体逻辑如下:
double maxValue(Item items[], int n, int capacity) {double totalValue = 0.0; // 初始化总价值为0int remaining = capacity; // 初始化剩余容量为背包总容量// 遍历排序后的物品数组(从单位价值最高的开始)for (int i = 0; i < n; i++) {// 情况1:当前物品可完整装入背包if (items[i].weight <= remaining) {totalValue += items[i].value; // 累加物品全部价值remaining -= items[i].weight; // 减少相应剩余容量}// 情况2:当前物品无法完整装入,装入剩余容量对应的部分else {// 部分物品的价值 = 剩余容量 / 物品总重量 * 物品总价值totalValue += (double)remaining / items[i].weight * items[i].value;remaining = 0; // 剩余容量耗尽,背包装满break; // 无需继续遍历,退出循环}}return totalValue; // 返回最终的最大总价值
}
五、完整流程与示例分析
1. 完整执行流程
整个问题的解决流程分为「输入 → 处理 → 输出」三阶段,逻辑清晰:
1. 输入阶段
- 用户输入物品数量 n 和背包容量 capacity(如输入 “3 50”,代表 3 个物品、背包容量 50kg)
- 依次输入每个物品的「重量」和「价值」(如输入 “10 60” “20 100” “30 120”)
- 计算每个物品的单位价值ratio(如第一个物品的 ratio = 60 / 10 = 6)
2. 处理阶段
- 调用 sort(items, items + n, cmp),按单位价值降序排序物品。
- 调用 maxValue(items, n, capacity),计算最大总价值。
3. 输出阶段:打印最终结果
2. 示例分步解析
以输入 “3 50;10 60;20 100;30 120” 为例,详细拆解处理过程:
- 步骤 1:排序后物品顺序:
物品 1(ratio=6)→ 物品 2(ratio=5)→ 物品 3(ratio=4)(单位价值从高到低)。
- 步骤 2:依次装入物品:
- 装入物品 1:总价值 = 60,剩余容量为 50-10=40;
- 装入物品 2:总价值 = 60+100=160,剩余容量为 40-20=20;
- 装入物品 3 的部分:剩余容量 20kg,可装入 20/30 的物品 3,价值增加 20/30*120=80,总价值 = 160+80=240,剩余容量 = 0,停止装入。
- 最终结果:总价值 240,与输出一致。
六、复杂度分析
1. 时间复杂度
整体时间复杂度由「排序操作」主导,具体如下:
- 排序操作:使用标准库sort函数(基于快速排序优化版本),时间复杂度为O(n log n)
- 贪心选择遍历:遍历n个物品,时间复杂度为O(n)
- 总时间复杂度:O(n log n)(因为排序耗时远大于遍历,可忽略O(n)项),适用于中等规模数据(如n≤10^5)
2. 空间复杂度
空间复杂度主要来自物品存储,为O(n):
- 仅需一个数组 items 存储 n 个物品的属性,无额外复杂数据结构,空间利用率高。
七、适用场景与局限性
1. 适用场景
该算法适用于满足「可分割」和「追求单位资源效益最大化」的场景,典型案例包括:
- 资源分配问题(如将有限资金按 “单位收益” 分配给不同项目,允许部分投资)
- 材料切割问题(如将固定长度的钢材按 “单位长度价值” 切割,最大化总价值)
- 能源分配问题(如将有限电力按 “单位功率收益” 分配给不同设备)
2. 局限性
该算法并非万能,存在以下适用边界:
- 不适用于「物品不可分割」的场景,此时需改用动态规划解决 0-1 背包问题。
- 无法处理「物品存在依赖关系」的场景(如选择 A 物品后才能选择 B 物品,或 A、B 物品不能同时选择),需结合图论或其他算法优化。
- 若存在 “重量为 0” 或 “价值为 0” 的异常物品,需在输入阶段增加验证逻辑,避免计算错误。