贪心算法实验:从理论到实践的完整指南
前言
贪心算法是计算机科学中一种重要的算法思想,它通过一系列局部最优选择来达到全局最优解。在本次实验中,我们将通过四个经典的编程问题,深入理解贪心算法的基本思路和实际应用。本文将详细介绍每个问题的分析过程、贪心策略设计以及完整的代码实现。
实验目标
本次实验的主要目标包括:
- 理解贪心算法的基本思路,实现增减字符串匹配、最大数问题等经典例题
- 掌握编写代码和调试程序的技巧
- 学会分析算法的时间复杂度
- 培养程序设计思路
- 提升撰写技术文档的能力
实验环境
硬件环境:PC微机
软件环境:
- Windows操作系统
- Python 3.7
- PyCharm
- Aistudio
- Jupyter Notebook
- Eclipse
贪心算法原理
贪心算法(Greedy Algorithm)的核心思想是:在每一步选择中都采取在当前状态下最好或最优的选择,即局部最优选择,从而希望导致结果是全局最优的。贪心算法并不一定总能得到全局最优解,但在许多情况下,特别是具有最优子结构的问题中,它能够有效地找到最优解。
贪心算法的适用条件:
- 贪心选择性质:每一步的局部最优选择能够导致全局最优解
- 最优子结构:问题的最优解包含其子问题的最优解
实验内容
一、增减字符串匹配
问题分析
给定一个由'I'和'D'组成的字符串s,需要构建一个长度为n+1的排列perm(其中n为s的长度),排列中的元素为[0,n]内的所有整数。排列需满足:
- 若s[i]为'I',则perm[i] < perm[i+1]
- 若s[i]为'D',则perm[i] > perm[i+1]
核心是找到一种符合相邻元素大小关系的排列方式,且需包含0到n的所有整数。
贪心策略设计
采用双指针贪心思路,维护两个指针low和high,分别初始化为0和n。
- 当遇到字符'I'时,选择当前最小的可用数字(即low)加入排列,因为需要下一个元素更大,保留较大数字供后续使用
- 当遇到字符'D'时,选择当前最大的可用数字(即high)加入排列,因为需要下一个元素更小,保留较小数字供后续使用
- 遍历结束后,将剩余的最后一个数字(此时low与high相等)加入排列
编码实现
python
def strsum(s):
n = len(s)
low, high = 0, n
perm = []
for char in s:
if char == 'I':
perm.append(low)
low += 1
else:
perm.append(high)
high -= 1
perm.append(low)
return perm
test = "IDID"
print(strsum(test)) # 输出示例:[0, 4, 1, 3, 2]
二、最大数问题
问题分析
给定一组非负整数,需要重新排列每个数的顺序(每个数不可拆分),使它们组成一个最大的整数。由于输出结果可能非常大,需返回字符串形式。
核心是确定两个数字的排列顺序,以保证拼接后的结果最大。例如对于3和30,330大于303,因此3应排在30之前。
贪心策略设计
贪心策略体现在排序规则上:对于任意两个数字x和y,将它们转换为字符串后,比较拼接结果xy和yx的大小。
- 若xy的字典序大于yx,则x应排在y之前
- 否则y排在x之前
通过这种自定义排序规则,将数组中的数字排序后拼接,即可得到最大整数。同时需处理特殊情况,如排序后结果以0开头(如[0,0]),需返回"0"而非"00"。
编码实现
python
from functools import cmp_to_key
def largestNumber(nums):
# 转换为字符串列表
str_nums = list(map(str, nums))
# 自定义比较函数:若xy > yx,则x应在y前
def compare(x, y):
if x + y > y + x:
return -1 # x排在y前
else:
return 1 # y排在x前
# 按自定义规则排序
str_nums.sort(key=cmp_to_key(compare))
# 处理全零情况
if str_nums[0] == '0':
return '0'
# 拼接结果
return ''.join(str_nums)
print(largestNumber([3,30,34,5,9])) # 输出:"9534330"
三、买卖股票的最佳时机 II
问题分析
给定一支股票的价格序列,每天可以决定购买或出售股票,且任何时候最多持有一股股票(可当天买入后卖出)。目标是计算能获得的最大利润。
核心是捕捉所有的上涨区间,因为多次买卖可以累积利润。例如在价格从1涨到5时,1买入5卖出可获利4,与1买入3卖出再3买入5卖出的总利润相同。
贪心策略设计
贪心思路是只要当天的股票价格高于前一天的价格,就将两者的差价计入总利润。因为可以在当天卖出前一天买入的股票,再立即买入当天的股票(若后续仍上涨),这样每一段上涨的差价都能被捕捉到。
通过累积所有正的相邻差价,即可得到最大利润。
编码实现
python
def maxProfit(prices):
max_profit = 0
for i in range(1, len(prices)):
# 若当天价格高于前一天,累加差价
if prices[i] > prices[i-1]:
max_profit += prices[i] - prices[i-1]
return max_profit
# 测试示例
test_cases = [
[7, 1, 5, 3, 6, 4], # 预期输出:7
[1, 2, 3, 4, 5], # 预期输出:4
[7, 6, 4, 3, 1] # 预期输出:0
]
for prices in test_cases:
print(f"输入:{prices},输出:{maxProfit(prices)}")
四、分发糖果问题
问题分析
一群孩子站成一排,每个孩子有评分,需分发糖果并满足:
- 每个孩子至少1个糖果
- 若一个孩子的评分高于相邻孩子,则他的糖果数必须多于该相邻孩子
目标是找到最少需要的糖果总数。核心是同时满足左右两侧的约束,避免因单侧判断导致的遗漏。
贪心策略设计
采用两次遍历的贪心策略:
- 第一次从左到右遍历,若右边孩子的评分高于左边,则右边孩子的糖果数更新为左边孩子的糖果数加1(保证左邻约束)
- 第二次从右到左遍历,若左边孩子的评分高于右边,且左边孩子当前的糖果数不大于右边,则左边孩子的糖果数更新为右边孩子的糖果数加1(保证右邻约束)
两次遍历后,所有约束均被满足,且总糖果数最少。
编码实现
python
def candy(ratings):
n = len(ratings)
if n == 0:
return 0
# 初始化每个孩子至少1个糖果
candies = [1] * n
# 从左到右:处理右边评分高于左边的情况
for i in range(1, n):
if ratings[i] > ratings[i-1]:
candies[i] = candies[i-1] + 1
# 从右到左:处理左边评分高于右边的情况
for i in range(n-2, -1, -1):
if ratings[i] > ratings[i+1]:
candies[i] = max(candies[i], candies[i+1] + 1)
return sum(candies)
# 测试示例
test_cases = [
[1, 0, 2], # 预期输出: 5
[1, 2, 2], # 预期输出: 4
[1, 3, 2, 2, 1] # 预期输出: 7
]
for i, ratings in enumerate(test_cases):
result = candy(ratings)
print(f"测试用例 {i+1}: {ratings},最少需要糖果数: {result}")
结果分析
四个编程题的求解结果均符合问题要求,其核心原因在于所采用的算法策略能够精准匹配问题的约束与目标。
对于增减字符串匹配问题,通过双指针贪心策略重构排列时,low和high的动态调整确保了每一步都能满足"I"或"D"的相邻关系要求,最终加入剩余数值后,排列既包含[0,n]的所有整数,又完全符合字符串s的大小关系描述。
最大数问题中,通过自定义排序规则(比较两个数字拼接后的字符串大小)实现最大整数的构建,结果的正确性源于该规则能确保每对数字的排列都是局部最优的------即对于任意两个数字x和y,x在y前总能使拼接结果更大。
股票最大利润求解中,贪心策略通过累积所有上涨区间的差价,本质上与动态规划思路一致------因为多次买卖的总利润等价于捕捉所有正向波动,有效利用了"可当天买卖"的规则。
分发糖果问题中,两次遍历的贪心策略有效平衡了左右两侧的约束条件。第一次左到右遍历确保右侧评分更高的孩子获得更多糖果,第二次右到左遍历通过max函数修正左侧评分更高的孩子的糖果数,既避免了重复增加,又保证了所有相邻约束均被满足。
实验总结
本次实验通过求解四个典型编程问题,系统实践了贪心算法的核心思想,深化了对"问题分析---策略设计---代码实现---结果验证"完整解题流程的理解。
实验的核心收获在于明确了算法策略与问题特性的匹配原则:当问题可通过局部最优选择累积达成全局最优时,贪心算法是高效且简洁的解决方案。每个问题的策略设计都与问题的核心约束高度匹配,确保了结果的正确性与最优性。
在解题过程中,问题核心约束的精准把握是关键前提。例如分发糖果问题需同时满足"相邻高分多糖果"和"最少糖果数"的双重要求,因此设计了两次遍历的贪心策略;组成最大整数问题的核心在于数字拼接规则,因此自定义排序规则成为突破点。
此外,边界情况的处理能力也得到了强化,如全零数组的结果优化、空输入的判断,这些细节虽不影响核心算法逻辑,却直接决定了代码的健壮性和结果的正确性,是编程实践中不可或缺的环节。
本次实验不仅巩固了贪心算法的应用场景和实现技巧,更培养了结构化的解题思维:面对复杂问题时,先拆解核心约束与目标,再选择适配的算法策略,最后通过边界处理和结果验证完善代码。这些能力将有效支撑后续更复杂的算法问题求解。