贪心算法(最优装载问题)

贪心算法

贪心算法(Greedy Algorithm)是一种在每一步决策中都采取当前状态下最优选择 的启发式策略,其核心思想是通过**"局部最优"逐步逼近"全局最优"。**它不追求对所有可能情况的枚举,而是基于某种贪心策略快速做出选择,最终希望等到问题的最优解。

核心思想

贪心算法的本质是"视短"的,它只关注当前步骤中最好的选择(局部最优),不考虑该选择对未来步骤的影响,也不回溯修改之前决策。其逻辑可概括为:

  1. 对问题进行分解,将其拆分为一系列可独立决策的子问题;
  2. 定义一个 "贪心策略"(如 "选最小""选最早""选单位价值最高" 等),用于在每个子问题中选择局部最优解;
  3. 逐步执行这些局部最优选择,累积结果,最终形成全局解。

使用条件

并非所有问题都能用贪心算法求解,它仅适用于满足以下两个条件的问题:

  1. 贪心选择性质

    全局最优解可以通过一系列局部最优选择(贪心选择)得到。即,存在一种策略,使得每次选择的局部最优解能最终组合成全局最优解。

  2. 最优子结构

    问题的全局最优解包含其子问题的最优解。即,若将问题拆分为子问题,子问题的最优解可用于构建原问题的最优解。

基本步骤

  1. 问题建模:将问题转化为 "多步选择" 的形式,明确每个步骤的可选方案和目标(如 "最大化收益""最小化成本")。

  2. 确定贪心策略:设计一个规则,用于在每一步从可选方案中选择 "局部最优" 的选项(如 "选结束时间最早的活动""选单位重量价值最高的物品")。

  3. 证明策略有效性:验证该贪心策略是否满足 "贪心选择性质" 和 "最优子结构"(关键步骤,否则可能得到非最优解)。

  4. 逐步求解:按贪心策略依次选择,累积结果,得到最终解。

典型应用场景

贪心算法在许多经典问题中被证明有效,例如:

1. 活动选择问题

问题 :有若干活动,每个活动有开始时间和结束时间,选择最多的不重叠活动。
贪心策略 :每次选择结束时间最早 的活动,然后排除与其冲突的活动,重复此过程。
原理:结束时间最早的活动能留下更多时间给后续活动,最终可得到最多不重叠活动。

2. 哈夫曼编码(Huffman Coding)

问题 :为字符设计前缀码(无歧义编码),使总编码长度最短(压缩最优)。
贪心策略 :每次选择频率最低的两个字符 合并为一个新节点,重复此过程构建哈夫曼树,树的路径即为编码。
原理:频率低的字符用较长编码,频率高的用较短编码,总长度最优。

3. 最小生成树(Prim/Kruskal 算法)

问题:在连通图中选择 n-1 条边,使所有节点连通且总权重最小。

贪心策略

Prim 算法:从某节点出发,每次选择连接树内外的最小权重边

Kruskal 算法:按边权重从小到大排序,每次选择不形成环的边

原理:通过局部选择最小权重边,最终形成全局权重最小的生成树。

4. 单源最短路径(Dijkstra 算法)

问题 :从起点到其他所有节点的最短路径(边权重非负)。
贪心策略 :每次选择当前已知最短路径的节点 ,并用其更新相邻节点的路径长度。
原理:由于边权重非负,一旦某节点被选中,其最短路径即确定,无需回溯。

贪心算法的框架

例子:最优装载问题

要求:物品不可分割,要求装在最多货物。

思路:

(1)贪心策略:在所有货船里面每次选择重量最小的。

(2)局部最优解:根据贪心策略,一步一步地得到局部最优解,

例如:第一次选择一个重量最小的古董,记位置a1,第二次再从剩下的再选最小的记为a2,一次类推....

(3) 把所有的局部最优解合为原来问题的一个最优解(a1、a2......)

(4)设计古董重量的排序。(冒泡排序,快速排序都行)如图:

代码:

cpp 复制代码
#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;
}

结果:

相关推荐
程序员东岸5 小时前
《数据结构——排序(中)》选择与交换的艺术:从直接选择到堆排序的性能跃迁
数据结构·笔记·算法·leetcode·排序算法
程序员-King.5 小时前
day104—对向双指针—接雨水(LeetCode-42)
算法·贪心算法
神仙别闹5 小时前
基于C++实现(控制台)应用递推法完成经典型算法的应用
开发语言·c++·算法
Ayanami_Reii5 小时前
进阶数据结构应用-一个简单的整数问题2(线段树解法)
数据结构·算法·线段树·延迟标记
listhi5206 小时前
基于改进SET的时频分析MATLAB实现
开发语言·算法·matlab
Keep_Trying_Go6 小时前
基于Zero-Shot的目标计数算法详解(Open-world Text-specified Object Counting)
人工智能·pytorch·python·算法·多模态·目标统计
xl.liu7 小时前
零售行业仓库商品数据标记
算法·零售
confiself7 小时前
通义灵码分析ms-swift框架中CHORD算法实现
开发语言·算法·swift
做怪小疯子7 小时前
LeetCode 热题 100——二叉树——二叉树的层序遍历&将有序数组转换为二叉搜索树
算法·leetcode·职场和发展
CoderYanger7 小时前
递归、搜索与回溯-记忆化搜索:38.最长递增子序列
java·算法·leetcode·1024程序员节