贪婪算法,穷举法和动态规划算法 找零问题

目录

一.贪婪算法

1.1引述

问题描述:

如果选择贪婪算法找零:

贪婪算法实现思路:

1.2原理

本质:

1.3代码示例

二.穷举法

1.1原理

基本思想:

具体步骤:

优点:

缺点:

1.2代码示例

三.动态规划算法

1.1原理

1.1.1定义:

1.1.2核心思想:

1.1.3特征:

1.1.4基本思路:

1.2代码示例


一.贪婪算法

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,找给客人的硬币数是最少的。

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代码示例

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("没有找到解决方案")
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代码示例

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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值