1357 字
7 分钟
优化问题和贪心策略学习笔记
贪心算法(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)是一种通过把原问题分解为相对简单的子问题的方式来求解复杂问题的算法思想。
将复杂问题拆解成子问题,保存子问题的解,避免重复计算。它适用于具有以下两个特性的问题:
- 最优子结构:问题的最优解包含子问题的最优解
- 重叠子问题:在求解过程中,会反复遇到相同的子问题
斐波那契数列
自底向上: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\W | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
| 2 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 7 |
| 3 | 0 | 0 | 3 | 4 | 8 | 8 | 11 | 11 | 12 | 12 | 12 | 15 | 15 | 15 | 15 | 15 | 15 | 15 | 15 | 15 | 15 |
| 4 | 0 | 0 | 3 | 4 | 8 | 8 | 11 | 11 | 12 | 12 | 16 | 16 | 16 | 16 | 19 | 19 | 19 | 20 | 20 | 20 | 20 |
| 5 | 0 | 0 | 3 | 4 | 8 | 8 | 11 | 11 | 12 | 12 | 16 | 16 | 16 | 16 | 19 | 19 | 19 | 20 | 20 | 20 | 21 |
递归法的代码实现:
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)) 分享
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时









