一、前提声明
1、什么是贪心算法
贪心算法是一种在每一步选择中都采取在当前状态下最优的选择,从而希望能够达到全局最优的算法。在每个阶段,贪心算法都会选择局部最优解,而不考虑未来的结果。贪心算法通常不会回溯,即一旦做出了选择,就无法撤销。
特点:
局部最优性: 贪心算法每步选择都是在当前状态下做出的最优选择,即局部最优。
无后效性: 当前的选择不会影响未来的选择,即每个步骤的选择只与当前状态有关。
不回溯: 一旦做出选择,就不再改变。
贪心算法通常适用于那些具有最优子结构性质的问题,即问题的最优解可以通过一系列局部最优的选择得到。
贪心算法的应用领域很广泛,但并不是所有问题都适合使用贪心算法。在一些问题中,贪心算法可能无法找到全局最优解,因为它没有考虑未来可能的选择带来的影响。
举例:找零问题。假设有一些硬币,面额分别为1元、2元、5元、10元,现在要找零k元。贪心算法可以每次选择面额最大的硬币进行找零,这样通常可以得到较少数量的硬币,尽管并不总是能得到最少数量的硬币
二、习题练习
1、找零问题
假设我们有以下面额的硬币:1元、2元、5元、10元,现在需要找零15元。我们可以使用贪心算法来找到最少数量的硬币。
def greedy_change(coins,target):
coins.sort(reverse=True) # 将硬币额度降序排序
change = [] #用于存储找零的硬币列表
for coin in coins:
while target >= coin:
target -= coin
change.append(coin)
if target == 0:
return change
else:
return None
if __name__ == '__main__':
coins = [1,2,5,10]
target = 15
result = greedy_change(coins,target)
if result is not None:
print(f'找零{target}元的最少硬币数为:{len(result)},硬币列表为{result}')
else:
print(f'找零失败,无法凑成目标金额')
这个程序使用贪心算法,每次选择面额最大的硬币,直到凑够目标金额。在这个例子中,程序会输出找零15元的最少硬币数量及硬币列表。贪心算法在这种情境下通常能够得到较好的结果,但并不总是能找到全局最优解。
2、活动选择
一个典型的实际问题是活动选择问题(Activity Selection Problem)。在这个问题中,有一组活动,每个活动都有一个开始时间和结束时间。活动之间不能重叠,即同一时刻只能进行一个活动。目标是找到一个最大的互不重叠的活动集合。
活动一:开始时间13:30,结束时间16:30
活动二:开始时间15:30,结束时间17:30
活动三:开始时间12:00,结束时间18:00
活动四:开始时间17:30,结束时间19:30
活动五:开始时间20:30,结束时间21:30
活动六:开始时间17:30,结束时间21:30
求解在12:00到21:30这个时间段最大的互不重叠的活动集合。
'''
activities.sort(key=lambda x: x[2])这行代码使用了Python的sort函数进行排序,其中的key参数指定了排序的依据。
activities 是一个包含活动信息的列表,每个元素是一个元组,包含活动的名称、开始时间和结束时间。
lambda x: x[2] 是一个匿名函数,用于定义排序的关键字。在这里,x 代表列表中的一个元素,x[2] 表示取该元素的第三个元素,即活动的结束时间。
因此,key=lambda x: x[2] 的作用是告诉 sort 函数按照每个元素的结束时间来排序。
'''
def activity_selection(activities):
# 按照活动的结束时间升序排序
activities.sort(key=lambda x: x[2]) # 取该元素的第三个元素
# 初始化已选活动列表,将第一个活动添加到列表中
selected_activities = [activities[0]]
# 记录上一个已选活动的结束时间
last_end_time = activities[0][2]
# 从第二个活动开始遍历
for activity in activities[1:]:
start_time,end_time = activity[1],activity[2] # 获取开始时间和结束时间
# 判断当前活动的开始时间是否在上一个活动的结束时间之后
if start_time >= last_end_time:
# 如果是,则将当前活动添加到已选活动列表中,并更新上一个活动的结束时间
selected_activities.append(activity)
last_end_time = end_time
return selected_activities
if __name__ == '__main__':
activities = [
("活动一", "13:30", "16:30"),
("活动二", "15:30", "17:30"),
("活动三", "12:00", "18:00"),
("活动四", "17:30", "19:30"),
("活动五", "20:30", "21:30"),
("活动六", "17:30", "21:30"),
]
# 将时间字符串转换为元组,便于排序
act = [(name,start,end) for name,start,end in activities]
result = activity_selection(act)
print(f'最大互不重叠的集合为:{result}')
3、霍夫曼码
一个实际问题是霍夫曼编码(Huffman Coding),它是一种用于数据压缩的贪心算法。霍夫曼编码主要用于将字符转换为变长的二进制编码,以便在传输和存储数据时减小数据的大小。
将字符串abcdefgh转换为对应的霍夫曼码。
'''
一个实际问题是霍夫曼编码(Huffman Coding),它是一种用于数据压缩的贪心算法。霍夫曼编码主要用于将字符转换为变长的二进制编码,
以便在传输和存储数据时减小数据的大小。
将字符串abcdefgh转换为对应的霍夫曼码。
'''
import heapq
from collections import defaultdict
def huffman_coding(data):
# 统计字符频率
frequency = defaultdict(int)
for char in data:
frequency[char] += 1
# 构建优先队列(最小堆)用于构建霍夫曼树
heap = [[weight, [char, ""]] for char, weight in frequency.items()]
heapq.heapify(heap)
# 构建霍夫曼树
while len(heap) > 1:
lo = heapq.heappop(heap)
hi = heapq.heappop(heap)
for pair in lo[1:]:
pair[1] = '0' + pair[1]
for pair in hi[1:]:
pair[1] = '1' + pair[1]
heapq.heappush(heap, [lo[0] + hi[0]] + lo[1:] + hi[1:])
# 返回霍夫曼编码
return heap[0][1:]
if __name__ == '__main__':
# 示例
data = "abcdefgh"
huffman_code = huffman_coding(data)
print("字符对应的霍夫曼编码:")
for char, code in huffman_code:
print(f"{char}: {code}")
4、汽车加油
汽车加油问题:
假设有一辆汽车要从起点出发前往终点,途中有若干个加油站。汽车的油箱有限,每次加满油能够行驶一定的距离。我们知道每个加油站的位置和它能提供的汽油数量。
目标是选择合适的加油站,以确保汽车能够顺利到达终点。
示例:汽车从起点0出发,初始油量10,加油站信息为(3, 4)和(7, 3),终点为目标位置15,求最少加油次数?
import heapq
def min_refuel_stops(target, start_fuel, stations):
# 使用最大堆存储每个加油站的汽油数量
max_heap = []
# 记录已加油次数
refuel_count = 0
# 当前汽车的位置
current_position = 0
# 当前汽车的剩余油量
current_fuel = start_fuel
# 遍历每个加油站
for station in stations:
station_position, station_fuel = station
# 判断是否需要加油
while current_position + current_fuel < station_position:
# 如果无法到达当前加油站,选择之前加油站中提供最大汽油的加油站进行加油
if not max_heap:
return -1 # 无法到达终点
current_fuel += -heapq.heappop(max_heap)
refuel_count += 1
# 将当前加油站的汽油数量存入最大堆
heapq.heappush(max_heap, -station_fuel)
# 判断是否需要加油到达终点
while current_position + current_fuel < target:
# 如果无法到达终点,选择之前加油站中提供最大汽油的加油站进行加油
if not max_heap:
return -1 # 无法到达终点
current_fuel += -heapq.heappop(max_heap)
refuel_count += 1
return refuel_count
if __name__ == '__main__':
# 示例:汽车从起点0出发,初始油量10,加油站信息为(3, 4)和(7, 3),终点为目标位置15
target_distance = 15
initial_fuel = 10
fuel_stations = [(3, 4), (7, 3)]
result = min_refuel_stops(target_distance, initial_fuel, fuel_stations)
print("最少加油次数为:", result)