python算法进阶

Level up!

Posted by QQL on October 8, 2018

算法进阶

贪心算法

在对问题求解时,总是作出在当前看来最好的选择。
贪心算法并不保证会得到最优解,但是在某些问题上贪心算法的解就是最优解。

找零问题

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

t = [100, 50, 20, 5]  # 面值,需要进行排序sort/sorted

def change(t, n):
    """
    找零问题
    :param t: 面值列表 
    :param n: 需要零钱
    :return: 
    """
    m = [0 for _ in range(len(t))]  # 生成[0,0,0,0]对应每一个面值的个数
    for i, money in enumerate(t):  # 循环面值
        m[i] = n // money  # 面值对应区域 = 从最大面值开始,需要对面值列表进行排序
        n = n % money  # 剩余零钱
    return m, n

print(change(t, 376))

背包问题

一个小偷在某个商店发现有n个商品,第i个商品价值v i 元,重w i 千克。他希望拿⾛走的价值尽量高,但他的背包最多只能容纳W千克的东西。他应该拿走哪些商品?
0-1背包:对于一个商品,小偷要么把它完整拿走,要么留下。不能只拿走一部分,或把一个商品拿走多次。(商品为金条)
分数背包:对于一个商品,小偷可以拿走其中任意一部分。(商品为金砂)

goods = [(60, 10),(100, 20),(120, 30)]  # 每个商品元组表示(价格, 重量)
goods.sort(key=lambda x: x[0]/x[1], reverse=True)  # 每克商品的价格进行排序


def fractional_backpack(goods, w):
    """
    分数背包
    :param goods: 商品列表
    :param w: 背包容量
    :return:
    """
    m = [0 for _ in range(len(goods))]  # [0,0,0] 对应商品个数
    total_v = 0  # 价值总数
    for i, (prize, weight) in enumerate(goods):
        print(m)
        if w >= weight:  # 当背包容量大于商品,继续拿
            m[i] += 1  # 拿到对应商品的个数,如果拿完了
            total_v += prize  # 增加价值
            w -= weight  # 减少容量
        else:  # 容量不够
            m[i] = w / weight  # 取商品的几分之几,实现尽量多拿贪心算法
            total_v += m[i] * prize  # 价值
            w = 0  # 拿完了,容量=0
            break
    return total_v, m

print(fractional_backpack(goods, 500))

拼接最大数字问题

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

from functools import cmp_to_key

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


def xy_cmp(x, y):
    """
    函数必须有两个参数用来进行比较
    :param x:
    :param y:
    :return:
    """
    if x + y < y + x:  # x放前面
        print('<', x, y)
        return 1
    elif x + y > y + x:  # y放前面
        print('>',x,y)
        return -1
    else:  # 相同位置排序无所谓
        return 0


def number_join(li):
    li = list(map(str, li))  # 全部转换为str
    li.sort(key=cmp_to_key(xy_cmp))  # cmp_to_key(比较函数)
    return "".join(li)


print(number_join(li))

活动选择问题

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

思考
贪心结论:最先结束的活动一定是最优解的一部分。
证明:假设a是所有活动中最先结束的活动,b是最优解中最先结束的活动。
如果a=b,结论成立。
如果a≠b,则b的结束时间一定晚于a的结束时间,则此时用a替换掉最优解中的b,a一定不与最优解中的其他活动时间重叠,因此替换后的解也是最优解。

# 元组[0]=开始时间,元组[1]=结束时间
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))

贪心算法解决最优化问题

动态规划

斐波那契数列列:
练习:使用递归和非递归的方法来求解斐波那契数列的第n项
F n = F n−1 + F n−2

# 子问题的重复计算
def fibnacci(n):
    """
    递归法
    :param n:
    :return:
    """
    if n == 1 or n == 2:
        return 1
    else:
        return fibnacci(n-1) + fibnacci(n-2)




# 动态规划(DP)的思想 = 递推式 + 重复子问题
def fibnacci_no_recurision(n):
    """
    非递归
    :param n:
    :return:
    """
    f = [0,1,1]  # 先确定前2个数为1,1 0的作用是让f[2]=f[0]+f[1]
    if n > 2:
        for i in range(n-2):  # 去掉前两个
            num = f[-1] + f[-2]
            f.append(num)
    return f[n]

print(fibnacci_no_recurision(100))

欧几里得算法

约数:如果整数a能被整数b整除,那么a叫做b的倍数,b叫做a的约数
给定两个整数a,b,两个数的所有公共约数中的最⼤大值即为最大公约数(Greatest Common Divisor, GCD)。
:12与16的最⼤大公约数是4

欧几里得算法:gcd(a, b) = gcd(b, a mod 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))

RSA加密算法

传统密码:加密算法是秘密的
现代密码系统:加密算法是公开的,密钥是秘密的
对称加密
⾮非对称加密
RSA⾮非对称加密系统
公钥:⽤用来加密,是公开的
私钥:⽤用来解密,是私有的
应用linuxSSHhttps的s
加密算法过程

  1. 随机选取两个质数p和q
  2. 计算n=pq
  3. 选取一个与φ(n)互质的小奇数e,φ(n)=(p-1)(q-1)
  4. 对模φ(n),计算e的乘法逆元d,即满足 (e*d) mod φ(n) = 1
  5. 公钥(e, n) 私钥(d, n)
    ``` p = 53 q = 59 n = pq # 3127 fai = (p-1)(q-1) # 3016 e = 3 fai / e # 1005.3333333333334

e为与fei互质的最小奇数

d = 2011 # 满足(ed)%fei=1 (ed)%n # 2906 (e*d)%fai # 1 ```
m为传入的数据明文,mod为取膜
用公钥加密过程:c = (m^e) mod n
用私钥解密过程:m = (c^d) mod n