贪心算法 是一种在每一步选择中都采取当前最优决策,希望通过局部最优解达到全局最优解的算法思想。它简单高效,但并非所有问题都适用。以下是贪心算法的核心概念、适用条件、经典问题及实现方法:
一、核心概念
- 局部最优解
- 在每一步选择中,贪心算法只考虑当前状态下的最优解,而不考虑后续步骤的影响。
- 无后效性
- 当前的选择不会影响后续选择的状态。
- 全局最优解
- 通过一系列局部最优解,最终希望得到全局最优解。
二、适用条件
贪心算法适用于满足以下条件的问题:
- 贪心选择性质
- 问题的全局最优解可以通过一系列局部最优解得到。
- 最优子结构
- 问题的最优解包含其子问题的最优解。
- 无后效性
- 当前的选择不会影响后续选择的状态。
三、经典问题与代码
1. 零钱兑换(硬币问题)
问题描述:给定不同面额的硬币和一个总金额,求最少需要多少枚硬币才能凑成总金额。
python
def coinChange(coins, amount):
coins.sort(reverse=True) # 从大到小排序
count = 0
for coin in coins:
if amount == 0:
break
count += amount // coin
amount %= coin
return count if amount == 0 else -1
# 示例
coins = [1, 2, 5]
amount = 11
print(coinChange(coins, amount)) # 输出 3 (5+5+1)
2. 活动选择问题
问题描述:给定一组活动,每个活动有开始时间和结束时间,求最多能安排多少个互不冲突的活动。
python
def activitySelection(activities):
activities.sort(key=lambda x: x[1]) # 按结束时间排序
selected = []
last_end = -1
for start, end in activities:
if start >= last_end:
selected.append((start, end))
last_end = end
return selected
# 示例
activities = [(1, 4), (3, 5), (0, 6), (5, 7), (8, 9)]
print(activitySelection(activities)) # 输出 [(1, 4), (5, 7), (8, 9)]
3. 最小生成树(Kruskal算法)
问题描述:给定一个带权无向图,求其最小生成树。
python
def kruskal(graph):
edges = sorted(graph['edges'], key=lambda x: x[2]) # 按权重排序
parent = {node: node for node in graph['nodes']}
result = []
def find(u):
while parent[u] != u:
parent[u] = parent[parent[u]] # 路径压缩
u = parent[u]
return u
for u, v, w in edges:
if find(u) != find(v):
result.append((u, v, w))
parent[find(u)] = find(v)
return result
# 示例
graph = {
'nodes': ['A', 'B', 'C', 'D'],
'edges': [('A', 'B', 1), ('B', 'C', 2), ('C', 'D', 3), ('A', 'D', 4)]
}
print(kruskal(graph)) # 输出 [('A', 'B', 1), ('B', 'C', 2), ('C', 'D', 3)]
四、贪心算法的局限性
- 不保证全局最优
- 贪心算法只能保证局部最优,不一定能得到全局最优解。
- 适用问题有限
- 只有满足贪心选择性质和最优子结构的问题才能使用贪心算法。
- 需要证明正确性
- 使用贪心算法时,必须证明其局部最优解能导致全局最优解。
五、适用问题特征
- 问题可以分解为一系列子问题。
- 子问题的最优解能组合成全局最优解。
- 常见问题包括:区间调度、最小生成树、最短路径、零钱兑换等。
贪心算法是一种高效的算法思想,但需要仔细分析问题是否满足其适用条件。在实际应用中,通常需要结合其他算法(如动态规划)来解决更复杂的问题。