贪心算法(最优装载问题)
贪心算法
贪心算法(Greedy Algorithm)是一种在每一步决策中都采取当前状态下最优选择的启发式策略,其核心思想是通过“局部最优”逐步逼近“全局最优”。它不追求对所有可能情况的枚举,而是基于某种贪心策略快速做出选择,最终希望等到问题的最优解。
核心思想
贪心算法的本质是“视短”的,它只关注当前步骤中最好的选择(局部最优),不考虑该选择对未来步骤的影响,也不回溯修改之前决策。其逻辑可概括为:
- 对问题进行分解,将其拆分为一系列可独立决策的子问题;
- 定义一个 “贪心策略”(如 “选最小”“选最早”“选单位价值最高” 等),用于在每个子问题中选择局部最优解;
- 逐步执行这些局部最优选择,累积结果,最终形成全局解。
使用条件
并非所有问题都能用贪心算法求解,它仅适用于满足以下两个条件的问题:
贪心选择性质
全局最优解可以通过一系列局部最优选择(贪心选择)得到。即,存在一种策略,使得每次选择的局部最优解能最终组合成全局最优解。最优子结构
问题的全局最优解包含其子问题的最优解。即,若将问题拆分为子问题,子问题的最优解可用于构建原问题的最优解。
基本步骤
问题建模:将问题转化为 “多步选择” 的形式,明确每个步骤的可选方案和目标(如 “最大化收益”“最小化成本”)。
确定贪心策略:设计一个规则,用于在每一步从可选方案中选择 “局部最优” 的选项(如 “选结束时间最早的活动”“选单位重量价值最高的物品”)。
证明策略有效性:验证该贪心策略是否满足 “贪心选择性质” 和 “最优子结构”(关键步骤,否则可能得到非最优解)。
逐步求解:按贪心策略依次选择,累积结果,得到最终解。
典型应用场景
贪心算法在许多经典问题中被证明有效,例如:
1. 活动选择问题
问题:有若干活动,每个活动有开始时间和结束时间,选择最多的不重叠活动。
贪心策略:每次选择结束时间最早的活动,然后排除与其冲突的活动,重复此过程。
原理:结束时间最早的活动能留下更多时间给后续活动,最终可得到最多不重叠活动。
2. 哈夫曼编码(Huffman Coding)
问题:为字符设计前缀码(无歧义编码),使总编码长度最短(压缩最优)。
贪心策略:每次选择频率最低的两个字符合并为一个新节点,重复此过程构建哈夫曼树,树的路径即为编码。
原理:频率低的字符用较长编码,频率高的用较短编码,总长度最优。
3. 最小生成树(Prim/Kruskal 算法)
问题:在连通图中选择 n-1 条边,使所有节点连通且总权重最小。
贪心策略:
Prim 算法:从某节点出发,每次选择连接树内外的最小权重边;
Kruskal 算法:按边权重从小到大排序,每次选择不形成环的边。
原理:通过局部选择最小权重边,最终形成全局权重最小的生成树。
4. 单源最短路径(Dijkstra 算法)
问题:从起点到其他所有节点的最短路径(边权重非负)。
贪心策略:每次选择当前已知最短路径的节点,并用其更新相邻节点的路径长度。
原理:由于边权重非负,一旦某节点被选中,其最短路径即确定,无需回溯。
贪心算法的框架
例子:最优装载问题
要求:物品不可分割,要求装在最多货物。
思路:
(1)贪心策略:在所有货船里面每次选择重量最小的。
(2)局部最优解:根据贪心策略,一步一步地得到局部最优解,
例如:第一次选择一个重量最小的古董,记位置a1,第二次再从剩下的再选最小的记为a2,一次类推....
(3) 把所有的局部最优解合为原来问题的一个最优解(a1、a2......)
(4)设计古董重量的排序。(冒泡排序,快速排序都行)如图:
代码:
#include<stdio.h>// 函数功能:用贪心算法选择最多数量的古董(优先选轻的)
// 参数:N-古董总数,W-最大载重,q-古董重量数组
int loadAntiques(int N, double W, double q[])
{int ans = 0; // 已装载的古董数量double totalWeight = 0.0; // 已装载的总重量// 1. 冒泡排序:将古董按重量从小到大排序(贪心策略基础)for (int i = 0; i < N - 1; i++){for (int j = 0; j < N - i - 1; j++){// 若前一个比后一个重,则交换(注意用double类型临时变量)if (q[j] > q[j + 1]){double temp = q[j]; // 修正:temp应为double类型(原代码用int错误)q[j] = q[j + 1];q[j + 1] = temp;}}}// 2. 贪心选择:从最轻的开始装,直到超重for (int i = 0; i < N; i++){// 修正:先判断加上当前古董是否超重,不超重才装载(原代码逻辑反了)if (totalWeight + q[i] <= W){totalWeight += q[i];ans++;}else{break; // 超重则停止装载}}// 输出结果printf("最大古董数量为:%d个\n", ans);printf("船上总重量为:%lf吨\n", totalWeight);return 0;
}int main()
{int N = 8; // 古董总数double W = 30.0; // 船的最大载重double q[8]; // 存储每个古董的重量// 修正:循环读取8个古董的重量(原代码只读取了1个)printf("请输入8个古董的重量(吨):\n");for (int i = 0; i < N; i++){scanf("%lf", &q[i]); // 注意取地址符&}// 调用函数计算最大装载量loadAntiques(N, W, q);return 0;
}
结果: