<think>我们刚刚讨论了C++中std::sort对重复元素的处理,现在用户想学习贪心算法的使用方法。用户提供了几个引用,包括贪心算法的基本思想、示例(如找零问题、过河问题)和代码片段(Java和C语言)。用户需求明确:要求示例代码和解释。根据引用[1][2],贪心算法的核心是“每一步选择当前最优解”。我将通过三个经典示例(找零问题、活动选择问题、过河问题)展示贪心算法的应用,并提供代码实现和详细解释。每个示例将包含:1.问题描述2.贪心策略分析(为什么可用贪心)3.代码实现(选择用户引用中出现的语言:Java和C,但用户没有指定本次要求的语言,考虑到引用[1]是Java,引用[3]是C,我将用Java写前两个示例,用C写过河问题)4.逐步解释代码逻辑注意:贪心算法不一定得到全局最优解,因此每个示例会讨论其正确性条件。示例1:找零问题(硬币问题)问题:用最少的硬币数凑出指定金额,假设硬币面额已排序(如1,5,10,25)贪心策略:每次选择不超过剩余金额的最大面额硬币代码(Java):示例2:活动选择问题问题:选择最多的互不重叠活动(每个活动有开始和结束时间)贪心策略:按结束时间升序排序,每次选择结束时间最早且不与已选活动重叠的活动代码(Java):示例3:过河问题(七人过桥)问题:七个人过桥,每次最多两人,必须有手电筒,过桥时间取两人中较慢者。求最短总时间。贪心策略:核心是减少手电筒来回送的时间。策略为:-让最快的人来回送手电筒-或者让最快的两个人配合送手电筒(减少返回时间)具体步骤(引用[3]的C语言代码实现):a.按过桥时间升序排序(假设时间数组为time[0]到time[6],time[0]最快)b.两种策略交替使用:策略1:最快的人带最慢的人过,然后最快的人返回(耗时:time[最慢]+time[最快])策略2:最快的两个人先过,最快的人返回;然后最慢的两人过,次快的人返回(耗时:time[1]+time[0]+time[最慢]+time[1])c.比较两种策略的耗时,选择较小的d.重复直到所有人都过桥代码(C语言):最后总结贪心算法使用步骤:1.分析问题是否具有贪心选择性质(局部最优能导致全局最优)2.确定贪心策略(每一步的选择标准)3.实现算法并验证正确性注意:过河问题较复杂,需详细解释策略选择的原因。我将按以下结构组织回答:一、贪心算法基本思想(引用[1][2])二、示例1:找零问题(Java代码)三、示例2:活动选择问题(Java代码)四、示例3:过河问题(C代码)五、贪心算法使用要点生成相关问题:1.贪心算法在哪些情况下不能得到最优解?请举例。2.如何证明贪心算法的正确性?3.动态规划与贪心算法的区别是什么?4.过河问题中,如果每次最多三人过桥,贪心策略如何调整?开始回答。</think>### 贪心算法使用指南(附代码示例)
贪心算法是一种**每一步选择当前最优解**的策略,最终希望达到全局最优解。其核心在于**贪心策略的选择**——不同问题需要设计不同的局部最优规则[^1][^2]。以下是使用贪心算法的详细步骤和经典示例:
---
#### 一、贪心算法使用步骤
1. **建立贪心选择标准**
定义每一步选择的最优解判定规则(如最大值、最小值等)
2. **做出局部最优选择**
基于标准选择当前步骤的最佳操作
3. **更新问题状态**
将问题简化为更小的子问题
4. **迭代求解**
重复上述过程直到问题解决
> ⚠️ **关键限制**:贪心算法**不一定**能得到全局最优解,需验证问题是否满足贪心选择性质(局部最优能推导全局最优)
---
#### 二、经典问题示例
##### 示例1:找零问题(最小硬币数)
**问题**:用面额 `{25, 10, 5, 1}` 的硬币凑出 `n` 分钱,求最少硬币数
**贪心策略**:优先使用最大面额硬币
**Java实现**:
```java
public class CoinChange {
public static int minCoins(int n) {
int[] coins = {25, 10, 5, 1};
int count = 0;
for (int coin : coins) {
while (n >= coin) {
n -= coin;
count++;
}
}
return count;
}
public static void main(String[] args) {
System.out.println(minCoins(67)); // 输出6 (25*2 + 10*1 + 5*1 + 1*2)
}
}
```
**执行流程**:
```
初始: n=67
1. 选25 -> n=42, count=1
2. 选25 -> n=17, count=2
3. 选10 -> n=7, count=3
4. 选5 -> n=2, count=4
5. 选1 -> n=1, count=5
6. 选1 -> n=0, count=6
```
##### 示例2:活动选择问题(最大兼容活动数)
**问题**:选择最多数量的互不重叠活动(每个活动有 `[start, end]` 时间)
**贪心策略**:优先选择结束时间最早的活动
**Java实现**:
```java
import java.util.*;
class Activity {
int start, end;
Activity(int s, int e) { start = s; end = e; }
}
public class ActivitySelection {
public static List<Activity> selectActivities(Activity[] activities) {
Arrays.sort(activities, (a, b) -> a.end - b.end); // 按结束时间排序
List<Activity> result = new ArrayList<>();
int lastEnd = Integer.MIN_VALUE;
for (Activity act : activities) {
if (act.start >= lastEnd) { // 检查是否兼容
result.add(act);
lastEnd = act.end;
}
}
return result;
}
public static void main(String[] args) {
Activity[] acts = {
new Activity(1, 4), new Activity(3, 5),
new Activity(0, 6), new Activity(5, 7)
};
List<Activity> selected = selectActivities(acts);
System.out.println("选择活动数: " + selected.size()); // 输出2
}
}
```
##### 示例3:过河问题(最短过桥时间)
**问题**:七人过桥,单独过桥时间 `{5,8,9,10,11,12,13}` 分钟,两人过桥按较慢者计时,每次最多两人同行,需手电筒[^3]
**贪心策略**:
1. 让最快两人先过桥
2. 最快者带回手电筒
3. 让最慢两人过桥
4. 次快者带回手电筒
5. 重复直到全部过桥
**C语言实现**:
```c
#include <stdio.h>
#include <stdlib.h>
// 升序排序函数
int compare(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
int minCrossingTime(int times[], int n) {
qsort(times, n, sizeof(int), compare); // 按过桥时间排序
int total = 0;
while (n > 3) {
// 策略1:最快带最慢 vs 策略2:最快两人配合带最慢两人
int plan1 = times[1] + times[0] + times[n-1] + times[1];
int plan2 = times[n-1] + times[0] * 2 + times[n-2];
total += (plan1 < plan2) ? plan1 : plan2;
n -= 2; // 一次送走两人
}
// 处理剩余人员
if (n == 3) total += times[0] + times[1] + times[2];
else if (n == 2) total += times[1];
else total += times[0];
return total;
}
int main() {
int times[] = {5,8,9,10,11,12,13};
printf("最短时间: %d分钟\n", minCrossingTime(times, 7));
// 输出: 最短时间: 65分钟
}
```
---
#### 三、贪心算法适用场景
| 问题类型 | 特点 | 示例 |
|------------------------|----------------------------------------------------------------------|--------------------------|
| **最优化问题** | 可分解为多步骤决策 | 找零问题、活动选择 |
| **贪心选择性质** | 局部最优能导向全局最优 | 最小生成树(Prim算法) |
| **无后效性** | 当前决策不影响后续状态 | 霍夫曼编码 |
| **高效近似解** | 当精确解计算复杂时提供高效近似方案 | 旅行商问题(TSP)近似解 |
---
#### 四、使用注意事项
1. **正确性验证**:证明问题满足贪心选择性质(如活动选择问题的最优子结构证明)
2. **排序预处理**:多数贪心算法需先排序(时间复杂度 $O(n \log n)$)
3. **边界处理**:注意循环终止条件和特殊输入(如空活动列表)
4. **替代方案**:当贪心失效时考虑动态规划(如背包问题)
> 💡 **设计技巧**:尝试将问题转化为"每次移除最优元素"或"每次添加最优元素"的模型
---
### 相关问题
1. 如何证明活动选择问题的贪心策略是最优的?
2. 在硬币找零问题中,若硬币面额为 `{4, 3, 1}`,求 `n=6` 时贪心策略为何失效?
3. 过河问题中,如果每次最多三人过桥,贪心策略如何调整?
4. 贪心算法与动态规划的核心区别是什么?请举例说明。
5. 霍夫曼编码如何体现贪心思想?其压缩效率如何计算?