python經常使用算法(6)——貪心算法,歐幾里得算法

1,貪心算法

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

  貪心算法並不保證會獲得最優解,可是在某些問題上貪心算法的解就是最優解。要會判斷一個問題可否用貪心算法來計算。貪心算法和其餘算法比較有明顯的區別,動態規劃每次都是綜合全部問題的子問題的解獲得當前的最優解(全局最優解),而不是貪心地選擇;回溯法是嘗試選擇一條路,若是選擇錯了的話能夠「反悔」,也就是回過頭來從新選擇其餘的試試。python

1.1  找零問題

  假設商店老闆須要找零 n 元錢,錢幣的面額有100元,50元,20元,5元,1元,如何找零使得所需錢幣的數量最少?(注意:沒有10元的面額)git

  那要是找376元零錢呢? 100*3+50*1+20*1+5*1+1*1=375github

  代碼以下:算法

# t表示商店有的零錢的面額
t = [100, 50, 20, 5, 1]

# n 表示n元錢
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)) # ([3, 1, 1, 1, 1], 0)

1.2  揹包問題

  常見的揹包問題有整數揹包和部分揹包問題。那問題的描述大體是這樣的。數組

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

  0-1揹包:對於一個商品,小偷要麼把他完整拿走,要麼留下。不能只拿走一部分,或把一個商品拿走屢次(商品爲金條)函數

  分數揹包:對於一個商品,小偷能夠拿走其中任意一部分。(商品爲金砂)學習

舉例:spa

  對於 0-1 揹包 和 分數揹包,貪心算法是否都能獲得最優解?爲何?

   顯然,貪心算法對於分數揹包確定能獲得最優解,咱們計算每一個物品的單位重量的價值,而後將他們降序排序,接着開始拿物品,只要裝得下所有的該類物品那麼就能夠全裝進去,若是不能所有裝下就裝部分進去直到揹包裝滿爲止。

  而對於此問題來講,顯然0-1揹包確定裝不滿。即便偶然能夠,可是也不能知足全部0-1揹包問題。0-1揹包(又叫整數揹包問題)還能夠分爲兩種:一種是每類物品數量都是有限的(bounded)。一種是數量無限(unbounded),也就是你想要的多少有多少,這兩種都不能使用貪心策略。0-1揹包是典型的第一種整數揹包問題。

  分數揹包代碼實現:

# 每一個商品元組表示(價格,重量)
goods = [(60, 10), (100, 20), (120, 30)]
# 咱們須要對商品首先進行排序,固然這裏是排好序的
goods.sort(key=lambda x: x[0]/x[1], reverse=True)

# w 表示揹包的容量
def fractional_backpack(goods, w):
    # m 表示每一個商品拿走多少個
    total_v = 0
    m = [0 for _ in range(len(goods))]
    for i, (prize, weight) in enumerate(goods):
        if w >= weight:
            m[i] = 1
            total_v += prize
            w -= weight
        # m[i] = 1 if w>= weight else weight / w
        else:
            m[i] = w / weight
            total_v += m[i]*prize
            w = 0
            break
    return m, total_v

res1, res2 = fractional_backpack(goods, 50)
print(res1, res2)  # [1, 1, 0.6666666666666666]

  

1.3  拼接最大數字問題

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

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

     注意1:字符串比較數字大小和整數比較數字大小不同!!! 字符串比較大小就是首先看第一位,大的就大,但是一個字符串長,一個字符串短如何比較呢?好比128和1286比較

  思路以下:

# 簡單的:當兩個等位數相比較
a = '96'
b = '97'

a + b if a > b else b + a

# 當出現了下面的不等位數相比較,如何使用貪心算法呢?
# 咱們轉化思路,拼接字符串,比較結果

a = '128'
b = '1286'

# 字符串相加
a + b = '1281286'
b + a = '1286128'

a + b if a + b > b + a else b + a

  數字拼接代碼以下:

from functools import cmp_to_key

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

def xy_cmp(x, y):
    # 其中1表示x>y,-1,0同理
    if x+y < y+x:
        return 1
    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)) # 94716321286128

補充:python  cmp_to_key函數

  下面學習一下Python中一個比較好用的模塊,就是functools 中的 cmp_to_key函數,這裏的 cmp_to_key就是在Python3中使用的,在Python2中就是 cmp函數。

  它的具體做用就是比較函數。固然上面函數也能夠寫成下面形式:

def largestNumber(self, nums):
        from functools import cmp_to_key
        temp = list(map(str, nums))
        temp.sort(key=cmp_to_key(lambda x, y: int(x + y) - int(y + x)), reverse=True)
        return ''.join(temp if temp[0] != '0' else '0')

  上面函數有兩個傳入的參數 x, y,當 x>y 時返回1 等於時候返回0,不然返回-1。其實我最上面的函數比較明顯。它在list的工做機制就是將列表中的元素去兩兩比較,當 cmp返回的時正數時交換兩元素。

  

1.4  活動選擇問題

  假設有 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):
    # 首先a[0] 確定是最先結束的
    res = [a[0]]
    for i in range(1, len(a)):
        if a[i][0] >= res[-1][1]:  # 當前活動的開始時間小於等於最後一個入選活動的結束時間
            # 不衝突
            res.append(a[i])
    return res

res = activity_selection(activities)
print(res)

 

1.5  最大子序和

  求最大子數組之和的問題就是給定一個整數數組(數組元素有負有正),求其連續子數組之和的最大值。下面使用貪心算法逐個遍歷。

 代碼以下:

def maxSubarray(li):
    s_max, s_sum = 0, 0
    for i in range(len(li)):
        s_sum += li[i]
        s_max = max(s_max, s_sum)
        if s_sum < 0:
            s_sum = 0

    return s_max

 

2,歐幾里得算法——最大公約數

2.1,最大公約數的定義

  約數:若是整數 a 能被整數 b 整除,那麼 a 叫作 b 的倍數,b 叫作 a 的約數。

  最大公約數(Greatest  Common  Divisor):給定兩個整數 a, b,兩個數的全部公共約數中的最大值即爲最大公約數。

  例如:12和16的最大公約數是 4 。

2.2,歐幾里得算法以下:

  歐幾里得算法又稱爲展轉相除法,用於計算兩個正整數a,b的最大公約數。

  • E:設兩個正整數a, b,且已知a>b
  • E1:令r = a%b('%'表明取餘)
  • E2:若r=0(即n整除m),結束運算,n即爲結果
  • E3:不然令a=b,b=r,並返回步驟E1

  歐幾里得算法運用了這樣一個等價式(設 gcd(a, b)表明 a 和 b 的最大公約數,mod()表明取餘運算或模運算)則:

 gcd(a,  b) =  gcd(b, a mod b ) = gcd(b, a%b)

  也就是說 m , n 的最大公約數等於他們相除餘數(r)和 n 的最大公約數。  

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

  意思就是 60對21取餘18,同理21對18餘3,18對3取餘0,因此3爲兩個數的最大公約數。

2.3,證實歐幾里得公式

  咱們的證實分爲兩步。第一步,證實gcd(a, b)是b, a%b 的一個公約數。第二步,證實這個公約數是最大的。

1,證實gcd(a, b)是b, a%b 的一個公約數

  1,由於任意兩個正整數都有最大公因數,設爲 d。

  2,將 a , b 分別用最大公因數 d 來表示爲 a = k1*d  b = k2*d (k1,k2是兩個常數)

  3,設 a = k*b + c (也就是a 除以 b 商 k 餘 c),而後把a = k1*d  b = k2*d  兩個式子中的 a,b代入式子,獲得:

c = a - k*b = k1*d - k * k2 * d,而後再提取公因數 d,獲得 c = (k1 - k2 * k)*d,這就說明,c也就是 a%b有 d 這個約數,由於開始咱們設 任意兩個數都有最大公約數d,因此 gcd(a, b) 是 b, a%b 的一個公約數。

  4,由此能夠獲得 c 是最大公因數 d 的倍數,得證:gcd(a, b) = gcd(b, a mod b)。因此以此類推,能夠將 m n中較大的數用較小的數的餘數 r 替換,實現了降維,因此有了E3的步驟。 

2,證實咱們求出來的公約數是最大的

  1,數學是一門嚴謹的學科,咱們須要嚴謹的正面,咱們知道 c(a%b) =  k1*d - k * k2 * d    b = k2*d,因此咱們只須要證實k1-k*k2, k2互質便可。

  2,這裏能夠用到反證法,咱們假設 k1 - k*k2 = q*t  k2=p*t,再講這個k1 代入最開始的 a=k1*d ,獲得 a=(q*t + k*k2)*d,再利用乘法分配律獲得: a = q*t*d + k*k2*d,這時候咱們發現,k2*d就是b,將其代入,獲得 a=q*t*d + b*d

  3,咱們在將k2 = p*t代入開始的b = k2*d,獲得b = p*t*d,再把這個式子代到a = q*t*d+b*d.獲得了:a = q*t*d+p*t*d.提取公因數:a=(q+p)*t*d

  4,再和b=p*t*d比較,發現他們的最大公因數變成了t*d和開始矛盾,因此假設不成立,反證成功!

2.4,如何計算最大公約數?

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

  2,《九章算術》:更相減損術

   代碼以下:

# 遞歸法:保證a>b
def gcd(a, b):
    if b == 0:
        return a
    else:
        return gcd(b, a % b)

# 遞推法
def gcd1(a, b):
    if a < b:
        a, b = b, a
    while b > 0:
        r = a % b
        a = b
        b = r
    return a

  由於這是一個僞遞歸,因此時間複雜度不高。

2.5,應用:實現分數計算

   利用歐幾里得算法實現一個分數類,支持分數的四則運算。

   代碼以下:

# _*_coding:utf-8_*_

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):
        # 1/12 + 1/20
        a = self.a
        b = self.b
        c = other.a
        d = other.b
        fenmu = self.zgs(b, d)
        femzi = a * (fenmu / b) + c * (fenmu / d)
        return Fraction(femzi, fenmu)

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


f = Fraction(30, 16)
print(f)

  

 2.7 歐幾里得算法的缺點

   歐幾里得算法是計算兩個數最大公約數的傳統算法,不管從理論仍是實際效率上都是很好地。可是卻有一個致命的缺陷,這個缺陷在素數比較小的時候通常是感覺不到的,只有在大素數時纔會顯現出來。

  通常實際應用中的整數不多會超過64位(固然如今已經容許128位),對於這樣的整數,計算兩個數之間的模很簡單。對於字長爲32位的平臺,計算兩個不超過32位的整數的模,只須要一個指令週期,而計算64位如下的整數模,也不過幾個週期而已。可是對於更大的素數,這樣的計算過程就不得不禁用戶來設計,爲了計算兩個超過64位的整數的模,用戶也許不得不採用相似於多位數除法手算過程當中的試商法,這個過程不但複雜,並且消耗了不少CPU時間。對於現代密碼算法,要求計算128位以上的素數的狀況比比皆是,設計這樣的程序迫切但願可以拋棄除法和取模。

  由J. Stein 1961年提出的Stein算法很好的解決了歐幾里德算法中的這個缺陷,Stein算法只有整數的移位和加減法,爲了說明Stein算法的正確性,首先必須注意到如下結論:

  gcd(a,a)=a,也就是一個數和其自身的公約數還是其自身。
  gcd(ka,kb)=k gcd(a,b),也就是最大公約數運算和倍乘運算能夠交換。特殊地,當k=2時,說明兩個偶數的最大公約數必然能被2整除。
  當k與b互爲質數,gcd(ka,b)=gcd(a,b),也就是約掉兩個數中只有其中一個含有的因子不影響最大公約數。特殊地,當k=2時,說明計算一個偶數和一個奇數的最大公約數時,能夠先將偶數除以2。

   代碼以下:

def gcd_Stein(a, b):  
    if a < b:
        a, b = b, a
    if (0 == b):
        return a
    if a % 2 == 0 and b % 2 == 0:
        return 2 * gcd_Stein(a/2, b/2)
    if a % 2 == 0:
        return gcd_Stein(a / 2, b)
    if b % 2 == 0:
        return gcd_Stein(a, b / 2)
    
    return gcd_Stein((a + b) / 2, (a - b) / 2)

  

 

傳送門:代碼的GitHub地址:https://github.com/LeBron-Jian/BasicAlgorithmPractice 

參考文獻:https://www.cnblogs.com/jason2003/p/9797750.html

https://www.cnblogs.com/Dragon5/p/6401596.html

相關文章
相關標籤/搜索