贪心算法实验1

贪心算法实验:从理论到实践的完整指南

前言

贪心算法是计算机科学中一种重要的算法思想,它通过一系列局部最优选择来达到全局最优解。在本次实验中,我们将通过四个经典的编程问题,深入理解贪心算法的基本思路和实际应用。本文将详细介绍每个问题的分析过程、贪心策略设计以及完整的代码实现。

实验目标

本次实验的主要目标包括:

  1. 理解贪心算法的基本思路,实现增减字符串匹配、最大数问题等经典例题
  2. 掌握编写代码和调试程序的技巧
  3. 学会分析算法的时间复杂度
  4. 培养程序设计思路
  5. 提升撰写技术文档的能力

实验环境

硬件环境:PC微机

软件环境

  • Windows操作系统
  • Python 3.7
  • PyCharm
  • Aistudio
  • Jupyter Notebook
  • Eclipse

贪心算法原理

贪心算法(Greedy Algorithm)的核心思想是:在每一步选择中都采取在当前状态下最好或最优的选择,即局部最优选择,从而希望导致结果是全局最优的。贪心算法并不一定总能得到全局最优解,但在许多情况下,特别是具有最优子结构的问题中,它能够有效地找到最优解。

贪心算法的适用条件:

  1. 贪心选择性质:每一步的局部最优选择能够导致全局最优解
  2. 最优子结构:问题的最优解包含其子问题的最优解

实验内容

一、增减字符串匹配

问题分析

给定一个由'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(保证左邻约束)
  2. 第二次从右到左遍历,若左边孩子的评分高于右边,且左边孩子当前的糖果数不大于右边,则左边孩子的糖果数更新为右边孩子的糖果数加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函数修正左侧评分更高的孩子的糖果数,既避免了重复增加,又保证了所有相邻约束均被满足。

实验总结

本次实验通过求解四个典型编程问题,系统实践了贪心算法的核心思想,深化了对"问题分析---策略设计---代码实现---结果验证"完整解题流程的理解。

实验的核心收获在于明确了算法策略与问题特性的匹配原则:当问题可通过局部最优选择累积达成全局最优时,贪心算法是高效且简洁的解决方案。每个问题的策略设计都与问题的核心约束高度匹配,确保了结果的正确性与最优性。

在解题过程中,问题核心约束的精准把握是关键前提。例如分发糖果问题需同时满足"相邻高分多糖果"和"最少糖果数"的双重要求,因此设计了两次遍历的贪心策略;组成最大整数问题的核心在于数字拼接规则,因此自定义排序规则成为突破点。

此外,边界情况的处理能力也得到了强化,如全零数组的结果优化、空输入的判断,这些细节虽不影响核心算法逻辑,却直接决定了代码的健壮性和结果的正确性,是编程实践中不可或缺的环节。

本次实验不仅巩固了贪心算法的应用场景和实现技巧,更培养了结构化的解题思维:面对复杂问题时,先拆解核心约束与目标,再选择适配的算法策略,最后通过边界处理和结果验证完善代码。这些能力将有效支撑后续更复杂的算法问题求解。

相关推荐
大炮走火2 小时前
iOS在制作framework时,oc与swift混编的流程及坑点!
开发语言·ios·swift
·云扬·2 小时前
【LeetCode Hot 100】 136. 只出现一次的数字
算法·leetcode·职场和发展
Xiaochen_123 小时前
有边数限制的最短路:Bellman-Ford 算法
c语言·数据结构·c++·程序人生·算法·学习方法·最简单的算法理解
马拉萨的春天5 小时前
iOS中如果一个大图500M直接加载显示会崩溃,那么如何设置加载显示呢?
macos·ios·cocoa
0***145 小时前
Swift资源
开发语言·ios·swift
z***I3945 小时前
Swift Tips
开发语言·ios·swift
J***Q2925 小时前
Swift Solutions
开发语言·ios·swift
Gavin-Wang5 小时前
Swift + CADisplayLink 弱引用代理(Proxy 模式) 里的陷阱
开发语言·ios·swift
大胆飞猪8 小时前
递归、剪枝、回溯算法---全排列、子集问题(力扣.46,78)
算法·leetcode·剪枝