贪心算法以及动态规划法

'''
# 贪心算法
贪心算法(贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择,
也就是说,
不从整体最优上加以考虑,他所做的是某种意义上的局部最优解

贪心算法并不保证会得到最优解,但是在某些问题上贪心算法的解就是最优解
要会判断一个问题能否用贪心算法来计算

'''
'''
找零问题,假设商店老板需要找零n元钱,钱币的面额有:
100元     50元     20元    5元    1元
如何找零使得所需钱币的数量最少
从最大钱币开始找
'''

t = [100, 50, 20, 5, 1]


def charge(t, n):
    m = [0 for _ in range(len(t))]
    for i, money in enumerate(t):
        m[i] = n // money
        n = n % money

    return m, n


print(charge(t, 376))

'''
背包问题:
    一个小偷在某个商店发现有n个商品,第i个商品价值vi元,重wi千克,他希望拿走的价值尽量高,但他的背包最多只能容乃w千克的东西
    他应该拿走哪些商品
    
    0-1背包问题 
            对于一个商品,小偷要么把它完整拿走,要么留下。不能只拿走一部分,或把一个商品拿走多次(商品为金条)
    分数背包问题
            对于一个商品,小偷可以拿走其中任意一部分(商品为金砂)
            
例子:
    商品1:v1=60 w1=10
    商品2:v2=100 w2=20
    商品3:v3=120 w3=30
    背包容量:W = 50
    
'''

goods = [(60, 10), (100, 20), (120, 30)]

goods.sort(key=lambda x: x[0] / x[1], reverse=True)


# 分数背包

def fenshu_backpack(goods, W):
    # print(goods)
    m = [0 for _ in range(len(goods))]
    total_v = 0
    for i, (prize, weight) in enumerate(goods):
        if W >= weight:
            m[i] = 1
            total_v += prize
            W -= weight
        else:
            m[i] = W / weight
            total_v += m[i] * prize
            W = [0]
            break

    return m, total_v


print(fenshu_backpack(goods, 50))

'''
拼接最大数字问题:
            有n个非负整数,将其按照字符串拼接的方式拼接为一个整数
            如何拼接可以使得得到的整数最大
            
例子;
    32,94,128,1286,6,71可以拼接出的最大整数为:
                                        94716321286128
'''
# 字符串比较的方式
from functools import cmp_to_key

shu = [32, 94, 128, 1286, 6, 71]


def xy_cmp(x, y):
    if x + y < y + x:
        return 1
    elif x + y > y + x:
        return -1
    else:
        return 0


def number_join(shu):
    shu = list(map(str, shu))
    shu.sort(key=cmp_to_key(xy_cmp))  # cmp传一个两个参数的函数
    # 这里也可以使用冒泡排序

    return "".join(shu)


print(number_join(shu))

'''
活动选择问题
        假设有n个活动,这些活动要占用同一片场地,而场地在某时刻只能供一个活动使用
        每个活动都有一个开始时间si和结束时间fi,表示活动在[si,fi]区间占用场地
        
        安排哪些活动能够使该场地举办的活动的个数最多?
        
        i    1    2   3   4   5   6   7   8   9   10   11
        
        si   1    3   0   5   3   5   6   8   8    2   12
        
        fi   4    5   6   7   9   9   10   11  12  14   16
        
'''
'''
贪心结论:最先结束的活动一定是最优解的一部分
证明: 
    假设a是所有活动中最先结束的活动,b是最优解中最先结束的活动
    如果a=b,结论成立
    如果a!=b,则b的结束时间一定晚于a的结束时间(剩余的时间可以分给其他活动),
    则此时用a换掉最优解中的b,a一定不与最优解中的其他活动时间重叠
    因此替换后的解也是最优解

'''

activities = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 9), (5, 9), (6, 10), (8, 11), (8, 12), (2, 14), (12, 16)]

# 保证活动是按照结束时间进行排序
activities.sort(key=lambda x: x[1])


def activity_selection(a):
    res = [a[0]]  # 进入入选活动序列
    for i in range(1, len(a)):
        if a[i][0] >= res[-1][1]:  # 当前活动的开始时间大于等于最后一个入选活动的结束时间
            # 不冲突
            res.append(a[i])
    return res


print(activity_selection(activities))

'''
动态规划法
    基因的比对
    序列的相似程度
    HMM
    

'''
'''
斐波那契数列
Fn = F(n-1) +F(n-2)
使用递归和非递归的方式来求解斐波那契数列的第n项

'''


# 递归方式
# 慢的原因: 子问题的重复计算

def fibnach(n):
    if n == 1 or n == 2:
        return 1
    else:
        return fibnach(n - 1) + fibnach(n - 2)


# 动态规划(DP)的问题:递推式 + 重复子问题

# 最优子结构

def fibnach_no_recurision(n):
    f = [0, 1, 1]
    if n >= 2:
        for i in range(n - 2):
            num = f[-1] + f[-2]
            f.append(num)

    return f[n]


print(fibnach_no_recurision(100))

'''
钢条切割问题
某公司出售钢条,出售价格与钢条长度之间的关系如下表:

长度i     1   2   3   4   5   6   7   8   9   10

价格pi    1   5   8   9   10  17  17  20  24  30

问题: 
    现有一段长度为n的钢条和上面的价格表,求切割钢条方案,使得总收益最大
'''
# 整数分割问题

'''
递推式:
    设长度为n的钢条切割后最优收益值为rn,可以得出推导式:
    rn = max(pn,r1+rn-1,r2+rn-2,...,rn-1+r1)
    第一个参数pn表示不切割
    
    其他n-1个参数分别表示另外n-1种不同切割方案
        将钢条切割为长度为i和n-i两端
        方案i的收益为切割两端的最优收益之和
    
    遍历所有的i,找到最大的收益的方案

递推式的简化:
        rn = max(pi + r(n-i))
        只对右边进行切割。左边不再切割
        
    
最优子结构:
        问题的最优解由相关子问题的最优解组合而成,这些问题可以独立求解
        
    
'''

import time


def cal_time(func):
    def wrapper(*args, **kwargs):
        t1 = time.time()
        result = func(*args, **kwargs)
        t2 = time.time()
        print("%s running time:%s secs." % (func.__name__, t2 - t1))
        return result

    return wrapper


p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 21, 23, 24, 26, 27, 27, 28, 30, 33, 36, 39, 40]


# 自顶向下递归实现
# 时间复杂度:O(2**n)

# 递归
def cut_rod_recurision_1(p, n):
    if n == 0:
        return 0
    else:
        res = p[n]
        for i in range(1, n):
            res = max(res, cut_rod_recurision_1(p, i) + cut_rod_recurision_1(p, n - i))

        return res


# print(cut_rod_recurision(p, 9))

# 递归

def cut_rod_recurision_2(p, n):
    if n == 0:
        return 0
    else:
        res = 0
        for i in range(1, n + 1):
            res = max(res, p[i] + cut_rod_recurision_2(p, n - i))
        return res


print(cut_rod_recurision_2(p, 9))

'''
动态规划解法
        每个子问题只求解一次,保存求解结果
        之后需要此问题时,只需要查找保存的结果
        
'''


# 自底向上
# 时间复杂度:O(n**2)

def cut_rod_dp(p, n):
    r = [0]
    for i in range(1, n + 1):
        res = 0
        for j in range(1, i + 1):
            mid = i - j
            res = max(res, p[j] + r[mid])
            if p[j] >= p[-1]:
                p.append(res)
        r.append(res)

    return r[n]


print(cut_rod_dp(p, 20))

'''
重构解
    对于每个子问题,保存切割一次时左边切下的长度
    
'''


def cut_rod_extend(p, n):
    r = [0]
    s = [0]
    for i in range(1, n + 1):
        res_r = 0  # 价格的最大值
        res_s = 0  # 价格最大值对应方案的左边不切割部分的长度
        for j in range(1, i + 1):
            mid = i - j
            if p[j] + r[mid] > res_r:
                res_r = p[j] + r[mid]
                res_s = j
        r.append(res_r)
        s.append(res_s)
    return s, r[n]


def cut_rod_solution(p, n):
    s, r = cut_rod_extend(p, n)
    ans = []
    while n > 0:
        ans.append(s[n])
        n -= s[n]
    return ans


#
s, r = cut_rod_extend(p, 10)
print(s)
print(cut_rod_dp(p, 20))
print(cut_rod_solution(p, 20))

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值