编程语言
首页 > 编程语言> > 算法进阶

算法进阶

作者:互联网

贪⼼心算法(⼜又称贪婪算法)

是指,在对问题求解时,总是做 出在当前看来是好的选择。也就是说,不不从整体优上加 以考虑,他所做出的是在某种意义上的局部优解。
贪⼼心算法并不不保证会得到优解,但是在某些问题上贪⼼心算 法的解就是优解。要会判断⼀一个问题能否⽤用贪⼼心算法来计 算。

找零问题:

假设商店⽼老老板需要找零n元钱,钱币的⾯面额有:100元、50元、 20元、5元、1元,如何找零使得所需钱币的数量量少?

t = [100, 50, 20, 5]

def change(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(change(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


对于0-1背包和分数背 包,贪⼼心算法是否都能 得到优解?为什什么?

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Date: 2018/4/14

# 分数背包
goods = [(60, 10),(100, 20),(120, 30)]  # 每个商品元组表示(价格, 重量)
goods.sort(key=lambda x: x[0]/x[1], reverse=True) # 排序按单位重量价格 降序


def fractional_backpack(goods, w): # w背包最大重量
    m = [0 for _ in range(len(goods))] # m取的排好序的商品数量
    total_v = 0
    for i, (prize, weight) in enumerate(goods):
        if w >= weight: # 包的重量大于等于商品重量
            m[i] = 1 # 表示这个商品拿多少 拿走所有的
            total_v += prize #拿走商品的价值
            w -= weight # 更新w值
        else: # 否则只拿一部分
            m[i] = w / weight # 拿走部分的数量
            total_v += m[i] * prize # 拿走部分商品的价值
            w = 0 # 更新w值
            break
    return total_v, m

print(fractional_backpack(goods, 50))

拼接最大数字问题:

有n个⾮非负整数,将其按照字符串串拼接的⽅方式拼接为⼀一个整数。 如何拼接可以使得得到的整数⼤大?
例:32,94,128,1286,6,71可以拼接除的⼤大整数为 94716321286128

from functools import cmp_to_key # 可以传一个老式的cmp函数可以转换成现在的key函数

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

def xy_cmp(x, y): #老式的cmp比较函数 排序规则
    if x+y < y+x: # 两个参数就是两个元素 如果 x+y < y+x
        return 1 # 返回1 表示x>y  他们俩要交换
    elif x+y > y+x:
        return -1
    else:
        return 0

def number_join(li):
    li = list(map(str, li))
    li.sort(key=cmp_to_key(xy_cmp))
    return "".join(li)

print(number_join(li))

活动选择问题

假设有n个活动,这些活动要占⽤用同⼀一⽚片场地,⽽而场地在某时 刻只能供⼀一个活动使⽤用。
每个活动都有⼀一个开始时间si和结束时间fi(题⽬目中时间以整数 表示),表示活动在[si, fi)区间占⽤用场地。 问:安排哪些活动能够使该场地举办的活动的个数多? i是序号

 

 

贪⼼心结论:先结束的活动⼀一定是优解的⼀一部分。
证明:假设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))

 

动态规划 

从斐波那契数列看动态规划

斐波那契数列列:

Fn =Fn−1+Fn−2

练习:使⽤用递归和⾮非递归的⽅方法来求解斐波那契数列列的第n项

# 递归(执行效率低)
# 子问题的重复计算
def fibnacci(n):
    if n == 1 or n == 2:
        return 1
    else:
        return fibnacci(n-1) + fibnacci(n-2)
        
# 子问题的重复计算       
# f(5)=f(4)+f(3)
# f(4)=f(3)+f(2)
# .......


# 非递归
# 动态规划(DP)的思想 = 递推式 + 重复子问题(把需要的子问题存起来)  
def fibnacci_no_recurision(n): f = [0,1,1] if n > 2: for i in range(n-2): num = f[-1] + f[-2] f.append(num) # 将算过的数加到f里 不会再重复计算了 return f[n] print(fibnacci_no_recurision(100))

 

钢条切割问题

某公司出售钢条,出售价格与钢条⻓长度之间的关系如下表:


问题:现有⼀一段⻓长度为n的钢条和上⾯面的价格表,求切割钢条 ⽅方案,使得总收益⼤大。

 

 

 钢条切割问题̶̶递推式:

设⻓度为n的钢条切割后最优收益值为rn,可以得出递推式:

第⼀个参数pn表示不切割

其他n-1个参数分别表示另外n-1种不同切割⽅案,对⽅案i=1,2,...,n-1

  将钢条切割为⻓度为i和n-i两段

  案i的收益为切割两段的最优收益之和

考察所有的i,选择其中收益最⼤的⽅案

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


# 第一种 会重复计算效率低
def cut_rod_recurision_1(p, n):
    if n == 0: # 如果长度是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

 

钢条切割问题̶̶最优子结构:

可以将求解规模为n的原问题,划分为规模更⼩的⼦问题:完成⼀次切割 后,可以将产⽣的两段钢条看成两个独⽴的钢条切个问题。

  组合两个⼦问题的最优解,并在所有可能的两段切割⽅案中选取组合收 益最⼤的,构成原问题的最优解。

  钢条切割满⾜最优⼦结构:问题的最优解由相关⼦问题的最优解组合⽽ 成,这些⼦问题可以独⽴求解。

  钢条切割问题还存在更简单的递归求解⽅法:

    从钢条的左边切割下⻓度为i的⼀段,只对右边剩下的⼀段继续进⾏切割,左 边的不再切割

  递推式简化为

    

  不做切割的⽅案就可以描述为:左边⼀段⻓度为n,收益为pn,剩余⼀段⻓度 为0,收益为r0=0


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

# 第二种 左边不切割 右边切割 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

 

 

递归算法由于重复求解相同⼦问题,效率极低

 

钢条切割问题̶̶动态规划解法 --自底向上实现

动态规划的思想:

  每个⼦问题只求解⼀次,保存求解结果

  之后需要此问题时,只需查找保存的结果

 

 

 

p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 21, 23, 24, 26, 27, 27, 28, 30, 33, 36, 39, 40] # 价格表 
# p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30]
# 第三种自底向上
@cal_time
def cut_rod_dp(p, n):
    r = [0]
    for i in range(1, n+1):
        res = 0
        for j in range(1, i+1):
            res = max(res, p[j] + r[i - j])
        r.append(res)
    return r[n]

最优解已经找到 那最优切割方案呢?

 

 

 

 s[i]就是切一刀下去不切的部分是多少就是左边是多少

序号是1的 切割方案 左边是1 右边是0 就是不切割

序号是2、3的也是同理

序号是4的 最后是 左边是2右边是2 切割

序号9是 切割左边是3右边是6 再看6 左边是6右边是0  所以9分割方案 为3和6 最有价格25

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

# 输出最优方案
def cut_rod_solution(p, n):
    r, s = cut_rod_extend(p, n)
    ans = []
    while n > 0:
        ans.append(s[n])
        n -= s[n]
    return ans


r, s = cut_rod_extend(p,20)
print(s)

print(cut_rod_dp(p, 20))
print(cut_rod_solution(p, 20))

 

 

 

 

最⻓公共子序列:

⼀个序列的⼦序列是在该序列中删去若⼲元素后得 到的序列。 例:“ABCD”和“BDF”都是“ABCDEFG”的⼦序列

最⻓公共⼦序列(LCS)问题:给定两个序列X和Y,求X和Y⻓度最⼤的公共⼦ 序列。

例:X="ABBCBDE" Y="DBBCDB" LCS(X,Y)="BBCD"

应⽤场景:字符串相似度⽐对

怎么找这个最长公共子序列呢

思考:暴⼒穷举法的时间复杂度是多少?

思考:最⻓公共⼦序列是否具有最优⼦结构性质?

 

 

 

 

 

 

 箭头指的是从哪来的值

 这个表怎么看

看末位置是4 再看序列对应位置是否相等 B和A不等 再找4是哪来的看箭头4是上来的 再看这个4位置的序列是否相等 相等就出去 依次类推 看4 3 2 1 相等位置分别是ABCB

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Date: 2018/9/9


# 推导出表中的数字
def lcs_length(x, y):
    m = len(x)
    n = len(y)
    c = [[0 for _ in range(n+1)] for _ in range(m+1)] # m+1行 n+1列 
    for i in range(1, m+1):
        for j in range(1, n+1):
            if x[i-1] == y[j-1]:    # i j 位置上的字符匹配的时候,来自于左上方+1
                c[i][j] = c[i-1][j-1] + 1
            else:
                c[i][j] = max(c[i-1][j], c[i][j-1])
    return c[m][n]

# 找箭头路径
def lcs(x, y):
    m = len(x)
    n = len(y)
    c = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
    b = [[0 for _ in range(n + 1)] for _ in range(m + 1)] # 和c同样位置记录箭头用的  定义 1 左上方 2 上方 3 左方
    for i in range(1, m+1):
        for j in range(1, n+1):
            if x[i-1] == y[j-1]:    # i j 位置上的字符匹配的时候,来自于左上方+1
                c[i][j] = c[i-1][j-1] + 1
                b[i][j] = 1 # 1 左上方 
            elif c[i-1][j] > c[i][j-1]: # 来自于上方  i是行
                c[i][j] = c[i-1][j]
                b[i][j] = 2 # 2 上方
            else:
                c[i][j] = c[i][j-1]
                b[i][j] = 3 # 3 左方
    return c[m][n], b


# 从最后一个位置回溯找箭头
def lcs_trackback(x, y):
    c, b = lcs(x, y)
    i = len(x)
    j = len(y)
    res = []
    while i > 0 and j > 0:
        if b[i][j] == 1:    # 来自左上方=>匹配 只有是斜箭头才是匹配的
            res.append(x[i-1]) # 拿出来村上
            i -= 1
            j -= 1
        elif b[i][j] == 2:  # 来自于上方=>不匹配
            i -= 1
        else: # ==3 来自于左方=>不匹配
            j -= 1
    return "".join(reversed(res))


print(lcs_trackback("ABCBDAB", "BDCABA"))

 

欧⼏⾥得算法:

最大公约数

约数:如果整数a能被整数b整除,那么a叫做b的倍数,b叫做 a的约数。

给定两个整数a,b,两个数的所有公共约数中的最⼤值即为最 ⼤公约数(Greatest Common Divisor, GCD)。

例:12与16的最⼤公约数是4

如何计算两个数的最⼤公约数:

欧⼏⾥得:辗转相除法(欧⼏⾥得算法)

《九章算术》:更相减损术

欧⼏⾥得算法:

gcd(a, b) = gcd(b, a mod b) # a mod b a模b a除以b取余

例:gcd(60, 21) = gcd(21, 18) = gcd(18, 3) = gcd(3, 0) = 3

def gcd(a, b):
    if b == 0:
        return a
    else:
        return gcd(b, a % b)

# 两种方法
def gcd2(a, b):
    while b > 0:
        r = a % b
        a = b
        b = r
    return a


print(gcd2(12,16))

利⽤欧⼏⾥得算法实现⼀个分数类,⽀持分数的四则运算

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Date: 2018/9/9

class Fraction:
    def __init__(self, a, b):
        self.a = a
        self.b = b
        x = self.gcd(a,b)
        self.a /= x
        self.b /= x

    def gcd(self, a, b):
        while b > 0:
            r = a % b
            a = b
            b = r
        return a

    def zgs(self, a, b):
        # 12 16 -> 4
        # 3*4*4=48
        x = self.gcd(a, b)
        return a * b / x

    def __add__(self, other):
        a = self.a
        b = self.b
        c = other.a
        d = other.b
        fenmu = self.zgs(b, d)
        fenzi = a * fenmu / b + c * fenmu / d
        return Fraction(fenzi, fenmu)



    def __str__(self):
        return "%d/%d" % (self.a, self.b)


a = Fraction(1,3)
b = Fraction(1,2)
print(a+b)

 

RSA加密算法简介

 

 

 

 

 

 

 

标签:return,进阶,res,self,range,算法,def,切割
来源: https://www.cnblogs.com/erhuoyuan/p/16302642.html