一、贪心算法原理
详细:
贪心算法(Greedy Algorithm)是一种在每一步选择中都采取最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。贪心算法并不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题它能产生整体最优解或者是整体最优解的近似解。
贪心算法的基本思路是从问题的某一个初始解出发,逐步逼近给定的目标,以尽可能快地求得更好的解。当达到某一步骤不能再继续前进时,算法停止。
贪心算法所得到的结果并不一定是问题的最优解,但是,在某些问题中,贪心算法却可以通过每一步的局部最优选择来达到全局最优解。
简述:
贪心算法是一种逐步构建解决方案的算法范式。每一步都选择在当前看来最优的选项,以期最终达到全局最优解。其基本思想是:
- 选择性质:从当前状态下的所有选择中选择一个最优的(贪心选择)。
- 可行性检查:确保选择不会违反问题的约束条件。
- 局部最优解:每一步选择的是局部最优解,期望最终形成全局最优解。
贪心算法的一个重要特点是每次选择的结果不能影响到后面的选择,也就是说,每次选择都是独立的。
应用条件
贪心算法并不总是适用。使用贪心算法的前提条件通常包括:
- 贪心选择性质:局部最优解能导致全局最优解。
- 最优子结构:一个问题的最优解包含其子问题的最优解。
二、贪心算法案例介绍
1.活动选择问题
假设有n个活动,每个活动都有一个开始时间和结束时间,这些活动使用同一个资源,例如同一个会议厅。问题是如何安排这些活动,使得尽可能多的活动能够被执行,而不会发生冲突(即一个活动进行时,另一个活动也在进行)。
贪心算法的思路是,首先选择结束时间最早的活动,然后从剩余活动中选择结束时间最早且不与已选活动冲突的活动,依此类推,直到没有可选活动为止。这种贪心策略可以保证所选的活动数量最多。
问题描述:给定一组活动,每个活动都有一个开始时间和结束时间。要选择尽可能多的互不重叠的活动,如何安排?
步骤:
- 按活动的结束时间从小到大排序。
- 选择第一个活动。
- 对于每个活动,若其开始时间大于等于上一个选择的活动的结束时间,则选择该活动。
代码示例:
def activity_selection(activities):
# 按活动结束时间排序
sorted_activities = sorted(activities, key=lambda x: x[1])
# 选择第一个活动
selected_activities = [sorted_activities[0]]
# 当前的结束时间
current_end_time = sorted_activities[0][1]
for activity in sorted_activities[1:]:
if activity[0] >= current_end_time:
selected_activities.append(activity)
current_end_time = activity[1]
return selected_activities
# 活动列表,每个活动以 (开始时间, 结束时间) 表示
activities = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 9), (5, 9), (6, 10), (8, 11), (8, 12), (2, 14), (12, 16)]
print(activity_selection(activities))
2.背包问题(分数背包问题的贪心解法)
假设有一个背包,其最大承重为W。有n个物品,每个物品都有自己的重量w_i和价值v_i。问题是如何选择物品放入背包,使得背包内物品的总价值最大,同时不超过背包的最大承重。
对于分数背包问题(即物品可以被分割),贪心算法的思路是,首先计算每个物品的单位价值(即价值除以重量),然后按照单位价值从大到小的顺序选择物品,直到背包满或没有可选物品为止。这种贪心策略可以保证背包内物品的总价值最大。
3.最小生成树问题(Prim算法和Kruskal算法)
在一个无向连通图中,最小生成树是一棵包含图中所有顶点的树,且所有边的权值之和最小。Prim算法和Kruskal算法都是基于贪心策略的求解最小生成树的算法。
Prim算法的思路是,从任意一个顶点开始,每次选择一条连接已选顶点和未选顶点中权值最小的边,然后将该边的另一个顶点加入已选顶点集合,直到所有顶点都被选中。
Kruskal算法的思路是,首先将所有边按照权值从小到大排序,然后从中选择边,每次选择一条边时都要保证加入该边后不会形成环,直到选择了n-1条边为止(n为顶点数量)。这种贪心策略可以保证所选的边构成的树权值之和最小。
问题描述:给定一个带权无向图,找到一个生成树,使得其边的权重之和最小。
步骤:
- 从任意一个节点开始,加入生成树。
- 从当前生成树连接的所有边中,选择一条权重最小且不形成环的边。
- 重复上述步骤直到所有节点都被包含在生成树中。
代码示例:
import heapq
def prim(graph, start):
mst = [] # 最小生成树
visited = set() # 已访问节点
min_heap = [(0, start)] # (权重, 节点)
while min_heap:
weight, u = heapq.heappop(min_heap)
if u in visited:
continue
visited.add(u)
mst.append((u, weight))
for v, w in graph[u]:
if v not in visited:
heapq.heappush(min_heap, (w, v))
return mst
# 图的邻接表表示
graph = {
'A': [('B', 1), ('C', 4)],
'B': [('A', 1), ('C', 2), ('D', 5)],
'C': [('A', 4), ('B', 2), ('D', 1)],
'D': [('B', 5), ('C', 1)]
}
print(prim(graph, 'A'))