mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
1357 字
7 分钟
优化问题和贪心策略学习笔记
2026-01-18

贪心算法(Greedy Algorithm)的定义#

贪心算法是一种解决问题的方法:

在每一步都做出当前看起来最优的选择,

期望通过一系列局部最优选择,最终得到全局最优解。

贪心算法的明显特征就是:只考虑当前状态,不做回溯,不保存子问题的解

找零问题#

def makeChange(coinValueList, change):
"""
使用贪心算法找零
coinValueList: 硬币面额列表,假设已排序(从大到小)
change: 需要找零的金额
"""
coins = []
for coin in coinValueList:
while change >= coin:
coins.append(coin)
change -= coin
return coins
# 示例:找零63分,使用面额为[25, 10, 5, 1]的硬币
print(makeChange([25, 10, 5, 1], 63))
# 输出: [25, 25, 10, 1, 1, 1] (6个硬币)

若存在21面额的硬币,还是用贪心策略的话结果还是6枚硬币,但是实际上最优解是3枚21面额的硬币

此时发生了贪心策略失效

找零问题:递归解法

首先确定基本结束条件:需要兑换的找零,其面值正好等于某种硬币

def recMC(coinValueList,change):
minCoins = change
if change in coinValueList:
return 1
else:
for i in [c for c in coinValueList if c <= change]:
numCoins = 1 + recMC(coinValueList,change - i)
if numCoins < minCoins:
minCoins = numCoins
return minCoins
print(recMC([1,5,10,25],63))

递归解法虽然能做出来但是极其低效,63分钱的问题居然需要67,716,925次递归调用

那么改进递归解法的关键就在于消除重复计算

我们可以用一个表将已经计算过的中间结果保存下来,在计算之前查表看是否已经计算过

**记忆化搜索(Memoization)**实现思路:

  • 在递归调用之前,查找表中是否已有部分找零的最优解
  • 如果有,直接返回最优解而不进行递归调用
  • 如果没有,才进行递归调用

代码实现:

def recDC(coinValueList,change,knownResults):
minCoins = change
if change in coinValueList: #递归基本结束条件
knownResults[change] = 1 #记录最优解
return 1
elif knownResults[change] > 0:
return knownResults[change] #查表成功,直接使用最优解
else:
for i in [c for c in coinValueList if c <= change]:
numCoins = 1 + recDC(
coinValueList,
change - i,
knownResults,
)
if numCoins < minCoins:
minCoins = numCoins
#找到最优解,记录到表中
knownResults[change] = minCoins
return minCoins
print(recDC([1,5,10,25],63,[0]*64))

动态规划(DP)#

动态规划(Dynamic Programming, DP)是一种通过把原问题分解为相对简单的子问题的方式来求解复杂问题的算法思想。

将复杂问题拆解成子问题,保存子问题的解,避免重复计算。它适用于具有以下两个特性的问题:

  1. 最优子结构:问题的最优解包含子问题的最优解
  2. 重叠子问题:在求解过程中,会反复遇到相同的子问题

斐波那契数列#

自底向上:DP 数组

def fib(n: int) -> int:
if n < 0:
raise ValueError("n must be non-negative")
if n <= 1:
return n
dp = [0] * (n + 1)
dp[0], dp[1] = 0, 1
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]

自顶向下:记忆化搜索(Memoization)

def fib(n: int) -> int:
if n < 0:
raise ValueError("n must be non-negative")
memo = {0: 0, 1: 1}
def dfs(k: int) -> int:
if k in memo:
return memo[k]
memo[k] = dfs(k - 1) + dfs(k - 2)
return memo[k]
return dfs(n)

找零问题#

def dpMakeChange(coinValueList,change,minCoins):
#从1分开始到change逐个计算最小硬币数
for cents in range(1,change + 1):
#初始化一个最大值
coinCount = cents
#减去每个硬币,向后查最小硬币数,同时记录总的最小数
for j in [c for c in coinValueList if c <= cents]:
if minCoins[cents - j] + 1 < coinCount:
coinCount = minCoins[cents - j] + 1
#得到当前最少硬币数,记录到表中
minCoins[cents] = coinCount
#返回最后一个结果
return minCoins[change]
print(dpMakeChange([1,5,10,21,25],63,[0]*64))

跟踪记录扩展

def dpMakeChange(coinValueList,change,minCoins,coinsUsed):
for cents in range(change + 1):
coinCount = cents
newCoin = 1 #初始化一下新加硬币
for j in [c for c in coinValueList if c <= cents]:
if minCoins[cents - j] + 1 < coinCount:
coinCount = minCoins[cents - j] + 1
newCoin = j #对应最小数量,所减的硬币
minCoins[cents] = coinCount
coinsUsed[cents] = newCoin
return minCoins[change]
def printCoins(coinsUsed,change):
coin = change
while coin > 0:
thisCoin = coinsUsed[coin]
print(thisCoin)
coin = coin - thisCoin

博物馆大盗问题#

m(i, W) 表示:

  • 在前i个宝物中选择
  • 背包容量为W时
  • 能获得的最大价值 对于第i个宝物,有两种选择:
m(i, W) = max {
m(i-1, W), // 不选第i个宝物
m(i-1, W-wᵢ) + vᵢ // 选第i个宝物(前提是 wᵢ ≤ W)
}

代码实现:

#宝物的重量和价值
tr = [None,{'w':2,'v':3},{'w':3,'v':4},{'w':4,'v':8},{'w':5,'v':8},{'w':9,'v':10}]
#大盗最大承重
max_w = 20
#初始化二维表格m[(i,w)]
m = {(i,w):0 for i in range(len(tr)) for w in range(max_w +1)}
#逐个填写二维表格
for i in range(1,len(tr)):
for w in range(1,max_w + 1):
if tr[i]['w'] > w: #装不下第i个宝物
m[(i,w)] = m[(i-1,w)] #不装第i个宝物
else:
# 不装第i个宝物,装第i个宝物,两种情况下的最大价值
m[(i,w)] = max(m[(i-1,w)],
m[(i-1,w-tr[i]['w'])] + tr[i]['v'])
print(m[(len(tr)-1,max_w)])

构建一个 m[i][W] 表格

i\W01234567891011121314151617181920
0000000000000000000000
1003333333333333333333
2003447777777777777777
3003488111112121215151515151515151515
4003488111112121616161619191920202020
5003488111112121616161619191920202021

递归法的代码实现:

tr = {(2,3),(3,4),(4,8),(5,8),(9,10)}
max_w = 20
# 初始化记忆化表格m
# key是(宝物组合,最大重量),value是最大价值
m = {}
def thief(tr,w):
if tr == set() or w == 0:
m[(tuple(tr),w)] = 0
return 0
elif(tuple(tr),w) in m:
return m[(tuple(tr),w)]
else:
vmax = 0
for t in tr:
if t[0] <=w:
#逐个从集合中去掉某个宝物,递归调用
#选出所有价值中最大值
v = thief(tr-{t},w-t[0]) + t[1]
vmax = max(vmax,v)
m[(tuple(tr),w)] = vmax
return vmax
print(thief(tr,max_w))
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

优化问题和贪心策略学习笔记
https://chaojixin.ren/posts/优化问题和贪心策略/
作者
超級の新人
发布于
2026-01-18
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00