贪心算法实验1
贪心算法实验:从理论到实践的完整指南
前言
贪心算法是计算机科学中一种重要的算法思想,它通过一系列局部最优选择来达到全局最优解。在本次实验中,我们将通过四个经典的编程问题,深入理解贪心算法的基本思路和实际应用。本文将详细介绍每个问题的分析过程、贪心策略设计以及完整的代码实现。
实验目标
本次实验的主要目标包括:
- 理解贪心算法的基本思路,实现增减字符串匹配、最大数问题等经典例题
- 掌握编写代码和调试程序的技巧
- 学会分析算法的时间复杂度
- 培养程序设计思路
- 提升撰写技术文档的能力
实验环境
硬件环境: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相等)加入排列
编码实现
def strsum(s):n = len(s)low, high = 0, nperm = []for char in s:if char == 'I':perm.append(low)low += 1else:perm.append(high)high -= 1perm.append(low)return permtest = "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"。
编码实现
from functools import cmp_to_keydef 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卖出的总利润相同。
贪心策略设计
贪心思路是只要当天的股票价格高于前一天的价格,就将两者的差价计入总利润。因为可以在当天卖出前一天买入的股票,再立即买入当天的股票(若后续仍上涨),这样每一段上涨的差价都能被捕捉到。
通过累积所有正的相邻差价,即可得到最大利润。
编码实现
def maxProfit(prices):max_profit = 0for 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(保证右邻约束)
两次遍历后,所有约束均被满足,且总糖果数最少。
编码实现
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函数修正左侧评分更高的孩子的糖果数,既避免了重复增加,又保证了所有相邻约束均被满足。
实验总结
本次实验通过求解四个典型编程问题,系统实践了贪心算法的核心思想,深化了对"问题分析—策略设计—代码实现—结果验证"完整解题流程的理解。
实验的核心收获在于明确了算法策略与问题特性的匹配原则:当问题可通过局部最优选择累积达成全局最优时,贪心算法是高效且简洁的解决方案。每个问题的策略设计都与问题的核心约束高度匹配,确保了结果的正确性与最优性。
在解题过程中,问题核心约束的精准把握是关键前提。例如分发糖果问题需同时满足"相邻高分多糖果"和"最少糖果数"的双重要求,因此设计了两次遍历的贪心策略;组成最大整数问题的核心在于数字拼接规则,因此自定义排序规则成为突破点。
此外,边界情况的处理能力也得到了强化,如全零数组的结果优化、空输入的判断,这些细节虽不影响核心算法逻辑,却直接决定了代码的健壮性和结果的正确性,是编程实践中不可或缺的环节。
本次实验不仅巩固了贪心算法的应用场景和实现技巧,更培养了结构化的解题思维:面对复杂问题时,先拆解核心约束与目标,再选择适配的算法策略,最后通过边界处理和结果验证完善代码。这些能力将有效支撑后续更复杂的算法问题求解。
