算法進階

 


貪心算法

貪心算法是指,在對問題求解時,老是作出在當前看來是最好的選擇。也就是說,不從總體最優上加以考慮,他所作出的是在某種意義上的局部最優解。算法

貪心算法並不保證會獲得最優解,可是在某些問題上貪心算法的解就是最優解。要會判斷一個問題可否用貪心算法來計算。app

1.找零問題

假設商店老闆須要找零n元錢,錢幣的面額有:100元,50元,20元,5元,1元,如何找零是的所需錢幣的數量最少?ide

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

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

print(change(t, 376))
View Code

二、揹包問題

一個小偷在某個商店發現有n個商品,第i個商品價值vi元,重wi千克。他但願拿走的價值儘可能高,但他的揹包最多隻能容納W千克的東西。他應該該走那些商品?加密

0-1揹包:對於一個商品,小偷要麼把它完整拿走,要麼留下。不能只拿走一部分,或則把一個商品拿走屢次。spa

分數揹包:對於一個商品,小偷能夠拿走其中任意一部分。code

舉例: 商品1:v1=60 w1=10blog

    商品2:v2=100 w2=20遞歸

            商品3:v3=120 w3=30ci

            揹包容量量:W=50字符串

goods = [(60, 10), (100, 20), (120, 30)]  # 每一個商品元組表示(價格,重量)
goods.sort(key=lambda x: x[0] / x[1], reverse=True)


def fractional_backpack(goods, w):
    m = [0 for i in range(len(goods))]
    total_v = 0
    for i, (price, weight) in enumerate(goods):
        if w >= weight:
            m[i] = 1
            total_v += price
            w -= weight
        else:
            m[i] = w / weight
            total_v += m[i] * price
            w = 0
            break
    return total_v, m

print(fractional_backpack(goods, 50))
分數揹包代碼實現

三、拼接最大數字問題

有n個非負整數,將其按照字符串拼接的方式拼接爲一個整數。如何拼接可使得獲得的整數最大?

例例:32,94,128,1286,6,71能夠拼接除的最⼤大整數爲
94716321286128

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


def number_join(li):
    li = list(map(str, li))

    for i in range(len(li) - 1):

        for j in range(len(li) - i - 1):
            if li[j] + li[j + 1] < li[j + 1] + li[j]:
                li[j], li[j + 1] = li[j + 1], li[j]

    return "".join(li)

print(number_join(li))
View Code

四、活動選擇問題

假設有n個活動,這些活動要佔⽤用同⼀一⽚片場地,⽽而場地在某時刻只能供⼀一個活動使⽤用。

每一個活動都有⼀一個開始時間si和結束時間fi(題⽬目中時間以整數表示),表示活動在[si, fi)區間佔⽤用場地。

貪心討論:最早結束的活動必定是最優解的一部分。

證實:假設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))
View Code

 

動態規劃

一、從斐波那契數列看動態規劃

斐波那契數列列:Fn = Fn−1 + Fn−2
練習:使⽤用遞歸和⾮非遞歸的⽅方法來求解斐波那契數列列的第n項

# 子問題的重複計算
def fibnacci(n):
    if n > 0:
        if n == 1 or n == 2:
            return 1
        else:
            return fibnacci(n-1) + fibnacci(n-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)
    return f[n]

四、鋼條切割問題

某公司出售鋼條,出售價格與鋼條⻓長度之間的關係以下表:

問題:現有⼀一段⻓長度爲n的鋼條和上⾯面的價格表,求切割鋼條⽅方案,使得總收益最⼤大。

 

鋼條切割問題 --遞推式

設⻓長度爲n的鋼條切割後最優收益值爲rn,能夠得出遞推式:rn = max(pn,r1+rn-1,r2+rn-2,…,rn-1+r1),

第⼀個參數pn表示不不切割
其餘n-1個參數分別表示另外n-1種不不同切割⽅方案,對方案i=1,2,...,n-1
將鋼條切割爲長度爲i和n-i兩段
方案i的收益爲切割兩段的最優收益之和
考察全部的i,選擇其中收益最大的⽅案

鋼條切割問題 --最優子結構

能夠將求解規模爲n的原問題,劃分爲規模更更小的子問題:完成⼀一次切割後,能夠將產⽣生的兩段鋼條當作兩個獨⽴立的鋼條切個問題。

組合兩個子問題的最優解,並在全部可能的兩段切割方案中選取組合收益最大的,構成原問題的最優解。

鋼條切割知足最優子結構:問題的最優解由相關⼦問題的最優解組合而成,這些子問題能夠獨立求解。

鋼條切割問題 --自頂向下遞歸實現

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

## 實現複雜度O(2n) 2的n次方, 效率指數型增加,效率很是慢

鋼條切割問題 --動態規劃解法

遞歸算法因爲重複求解相同子問題,效率極低

動態規劃的思想:

  1. 每一個子問題之求解一次,保存求解結果

  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):
            res = max(res, p[j] + r[i - j])
        r.append(res)
    return r[n]

## 時間複雜度爲O(n的平方)

鋼條切割問題 --重構解

如何修改動態規劃算法,使其不不僅輸出最優解,還輸出最優切割⽅方案?

對於每一個子問題,保存切割一次時左邊切下的長度

 

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):
            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

動態規劃問題關鍵特徵

  • 最優子結構
    • 原問題的最優解中涉及多少個子問題
    • 在肯定最優解使用哪些自問題時,須要考慮多少種選擇
  • 重疊子問題

五、最長公共子序列

一個序列的子序列是在該序列中刪去若干元素後得 到的序列。
例:「ABCD」和「BDF」都是「ABCDEFG」的⼦序列
最長公共⼦子序列(LCS)問題:給定兩個序列X和Y,求X和Y⻓度最⼤的公共⼦序列。
例:X="ABBCBDE" Y="DBBCDB" LCS(X,Y)="BBCD"
應⽤場景:字符串類似度⽐對

 

例如:要求a="ABCBDAB"與b="BDCABA"的LCS:
因爲最後⼀位"B"≠"A":
所以LCS(a,b)應該來源於LCS(a[:-1],b)與LCS(a,b[:-1])中更大的那⼀個

def lcs_length(x, y):
    m = len(x)
    n = len(y)
    c = [[0 for _ in range(n+1)] for _ in range(m+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)] # 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
            elif c[i-1][j] > c[i][j-1]: # 來自於上方
                c[i][j] = c[i-1][j]
                b[i][j] = 2
            else:
                c[i][j] = c[i][j-1]
                b[i][j] = 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,兩個數的全部公共約數中的最大值即爲最大公約數

例:12與16的最大公約數是4

如何計算兩個數的最大公約數:

歐幾里得:展轉相除法(歐幾里得算法)

九章算術:更相減損術

# 歐幾里得算法:gcd(a,b) = gcd(b, a mod b)
例:gcd(60,21) = gcd(21,18) = gcd(18,3) = gcd(3,0) = 3

 

RSA加密算法介紹

傳統加密:加密算法是祕密的

現代密碼系統:加密算法是公開的,密鑰是祕密的

  • 對稱加密
  • 非對稱加密

RSA非對稱加密系統:

公鑰:用來加密,是公開的

私鑰:用來解密,是私有的

## RSA加密算法過程
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)

加密過程:c = (m^e) mod n

解密過程:m = (c^d) mod n

相關文章
相關標籤/搜索