目录
一.贪婪算法
1.1引述
问题描述:
一小孩买了价值33元的糖果,并将100元的钱交给售货员,售货员希望用数目最少的硬币找零。
假设提供了数目不限的面值为25元,10元,5元和1元的硬币,该怎么找零,硬币的数目最少?
如果选择贪婪算法找零:
每一次选择应该使零钱数尽量大。为确保解法的可行性(所给零钱=要找的零钱),所选择的硬币不应使零钱总数超过最终所需的数目,而是要相等。
1.需要找零67元。
2.从面值大的开始计算,首先选择的是2个25元的硬币(总共50元,还剩余17元),17<25,第三枚硬币不能选择25元,而是要选择10元。
3.第3枚硬币选择10元的硬币(还剩7元),然后是5元,最后加入2个1元硬币。
贪婪算法实现思路:
在每一步,都贪心地选择最佳选择,并希望通过一系列局部最优解,能够产生一个整体问题最优解(全局最优解)。
1.2原理
贪婪算法采取逐步构造最优解。在每一个阶段,都做出一个看上去最优的决策,决策一旦做出,就不可再更改。做出贪婪决策的依据被称为贪婪准则。
本质:
每次都形成局部最优解。
即每次都处理出一个最好的方案,通过一系列步骤来构造问题的解,每一步对目前构造的部分解做出一个扩展,直到获得问题的完全解为止。
需要满足的3个条件:
1.可行性:必须满足问题的约束
2.局部最优:是当前步骤中所有可行选择中最佳的局部选择(局部最优解)
3.不可取消:一旦做出选择,在算法的后面步骤中就无法改变。
1.3代码示例
题目:面值为【25,20,10,5,1】的硬币不限数量,有客人买东西要找他零钱n,找给客人的硬币数是最少的。
python
def tanlan_coin(coins,n):
result = []
coins_count = 0
for coin in coins:
count = n // coin
result += [coin] * count
#[coin] * count:这将创建一个包含count个coin值的列表。+=:这是列表的加法赋值运算符,它将右侧的列表连接到左侧列表的末尾。
n -= coin * count
coins_count += count
print(result)
print("总硬币数=",coins_count)
coins = [25,20,10,5,1]
n1=93
tanlan_coin(coins,n1)
n2=40
tanlan_coin(coins,n2)
在上述例子中,如果找零的钱n=40,那么结果输出则为【25,10,5】,说明贪婪算法其实不能得到全局最优解的。
二.穷举法
1.1原理
穷举法也称为暴力搜索或暴力穷举。它是通过枚举所有可能的情况来解决问题的方法 。穷举法通常适用于问题的规模较小且空间较为有限的情况。
基本思想:
通过遍历所有可能的解,逐个检查这些解是否满足问题的要求。
具体步骤:
1.确定问题的解集合:确定问题可能的解的范围或取值范围
2.逐个枚举解集合中每一个可能的解。
3.对每一个可能的解,验证其是否符合问题的解。
4.如果符合要求,则将该解作为结果输出;如果不符合要求,则继续枚举下一个可能的解。
优点:
简单直观,适用于一些规模较小的问题,并且可以保证找到问题的解(如果存在)。
缺点:
在解集合较大或者问题复杂度较高时,需要枚举的解的数量可能会非常庞大,导致计算量过大,效率较低。
1.2代码示例
python
def find_min_coins(coins, n, current_combination=[], current_sum=0):
# 基本情况:如果当前金额等于n,返回当前组合的硬币数量
if current_sum == n:
return len(current_combination)
# 如果当前金额超过n或没有硬币了,返回一个很大的数表示不可行
if current_sum > n or not coins:
return float('inf')
# 最少硬币数量初始化为一个很大的数
min_coins_needed = float('inf')
# 遍历硬币,尝试每一种可能
for i in range(len(coins)):
# 选择当前硬币,并递归调用函数
next_combination = current_combination + [coins[i]]
next_sum = current_sum + coins[i]
# 穷举下一个金额
min_coins = find_min_coins(coins, n, next_combination, next_sum)
# 更新最少硬币数量
if min_coins != float('inf') and min_coins < min_coins_needed:
min_coins_needed = min_coins
return min_coins_needed
# 硬币面值
coins = [25, 20, 10, 5, 1]
# 需要找零的金额
n = 93
# 调用函数
min_coins_count = find_min_coins(coins, n)
# 如果找到了解决方案,打印最少硬币数
if min_coins_count != float('inf'):
print(f"最少需要的硬币数是:{min_coins_count}")
else:
print("没有找到解决方案")
python
def recMc_pro(coinValueList, change, knownResults):
'''
找零问题改进版,使用一个列表存放子问题的解,避免重复的递归调用
:param coinValueList: 面值列表
:param change: 钱
:return: 最少硬币数
'''
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 + recMc_pro(coinValueList, change - i, knownResults)
if numCoins < minCoins:
minCoins = numCoins
# 找到最优解, 记录到表中
knownResults[change] = minCoins
return minCoins
三.动态规划算法
1.1原理
1.1.1定义:
动态规划,简称DP,是一种求解多阶段决策过程最优化问题的方法。
在动态规划中,通过把原问题分解为相对简单的子问题,先求解子问题,再由子问题的解而得到原问题的解。
1.1.2核心思想:
1.把"原问题"分解为"若干个重叠的子问题",每个子问题的求解过程都构成一个"阶段"。在完成一个阶段的计算之后,动态规划方法才会执行下一个阶段的计算。
2.在求解子问题的过程中,按照"自顶向下的记忆化搜索方法"或者"自底向上的递推方法"求解出"子问题的解",把结果存储在表格中,当需要再次求解此子问题时,直接从表格中查询该子问题的解,从而避免了大量的重复计算。
看起来很像是分治法,但动态规划和分治法有两点不同:
1.适用于动态规划求解的问题,在分解之后得到的子问题往往时相互联系的,会出现若干个重叠的子问题。
2.使用动态规划方法会将这些重叠的子问题的解保存在表格里,供随后的计算查询使用,从而避免大量的重复计算。
1.1.3特征:
能够使用动态规划方法解决的问题必须满足这三个特征:
1.最优子结构性质:指的是一个问题的最优解包含其子问题的最优解
2.重叠子问题性质:指的是在求解子问题的过程中,有大量的子问题是重复的,一个子问题在下一阶段的决策中可能会被多次用到。如果有大量重复的子问题,那么只需要对其求解一次,然后用表格将结果存储下来,以后使用时可以直接查询,不需要再次求解。
3.无后效性:指的是子问题的解(状态值)只与之前阶段有光,而与后面阶段无关。当前阶段的若干状态值一旦确定,就不再改变,不会再受到后续阶段决策的影响。
1.1.4基本思路:
1.划分阶段:将原问题按顺序(时间顺序,空间顺序或其他顺序)分解为若干个相互联系的"阶段"。划分后的阶段一定是有序或者可排序的,否则问题无法求解。
这里的"阶段"指的是子问题的求解过程。每个子问题的求解过程都构成一个阶段,再完成前一阶段的求解后才会进行后一阶段的求解。
2.定义状态:将和子问题相关的某些变量(位置,数量,体积,空间等等)作为一个"状态"表示出来。状态的选择要满足无后效性。
一个状态对应一个或多个子问题,所谓某个状态下的值,值的就是这个状态所对应的子问题的解。
3.状态转移:根据上一阶段的状态和该状态下所能做出的决策,推导出下一阶段的状态。或者说根据相邻两个阶段各个状态之间的关系,确定决策,然后推导出状态间的相互转移方式。
4.初始条件和边界条件:根据问题描述,状态定义和状态转移方程,确定初始条件和边界条件。
5.最终结果:确定问题的求解目标,然后按照一定顺序求解每一个阶段的问题。最后根据状态转移方程的递推结果,确定最终结果。
1.2代码示例
python
def getmincounts(n,coins):
dp = [n+1]*(n+1)
dp[0] = 0
for item in range(1,n+1):
for coin in coins:
if(item-coin<0):
continue
dp[item] = min(dp[item],dp[item-coin]+1)
return dp[n]
coins = [25,20,10,5,1]
n = 40
result = getmincounts(n,coins)
print(result)