貪心算法(又稱貪婪算法)是指,在對問題求解時,老是作出在當前看來是最好的選擇。也就是說,不從總體最優上加以考慮,他所作出的的時在某種意義上的局部最優解。html
貪心算法並不保證會獲得最優解,可是在某些問題上貪心算法的解就是最優解。要會判斷一個問題可否用貪心算法來計算。貪心算法和其餘算法比較有明顯的區別,動態規劃每次都是綜合全部問題的子問題的解獲得當前的最優解(全局最優解),而不是貪心地選擇;回溯法是嘗試選擇一條路,若是選擇錯了的話能夠「反悔」,也就是回過頭來從新選擇其餘的試試。python
假設商店老闆須要找零 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)
常見的揹包問題有整數揹包和部分揹包問題。那問題的描述大體是這樣的。數組
一個小偷在某個商店發現有 n 個商品,第 i 個商品價值 Vi元,重 Wi 千克。他但願拿走的價值儘可能高,但他的揹包最多隻能容納W千克的東西。他應該拿走那些商品?app
0-1揹包:對於一個商品,小偷要麼把他完整拿走,要麼留下。不能只拿走一部分,或把一個商品拿走屢次(商品爲金條)函數
分數揹包:對於一個商品,小偷能夠拿走其中任意一部分。(商品爲金砂)學習
舉例:spa
顯然,貪心算法對於分數揹包確定能獲得最優解,咱們計算每一個物品的單位重量的價值,而後將他們降序排序,接着開始拿物品,只要裝得下所有的該類物品那麼就能夠全裝進去,若是不能所有裝下就裝部分進去直到揹包裝滿爲止。
而對於此問題來講,顯然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]
有 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中一個比較好用的模塊,就是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返回的時正數時交換兩元素。
假設有 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)
求最大子數組之和的問題就是給定一個整數數組(數組元素有負有正),求其連續子數組之和的最大值。下面使用貪心算法逐個遍歷。
代碼以下:
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
約數:若是整數 a 能被整數 b 整除,那麼 a 叫作 b 的倍數,b 叫作 a 的約數。
最大公約數(Greatest Common Divisor):給定兩個整數 a, b,兩個數的全部公共約數中的最大值即爲最大公約數。
例如:12和16的最大公約數是 4 。
歐幾里得算法又稱爲展轉相除法,用於計算兩個正整數a,b的最大公約數。
歐幾里得算法運用了這樣一個等價式(設 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爲兩個數的最大公約數。
咱們的證實分爲兩步。第一步,證實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和開始矛盾,因此假設不成立,反證成功!
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
由於這是一個僞遞歸,因此時間複雜度不高。
利用歐幾里得算法實現一個分數類,支持分數的四則運算。
代碼以下:
# _*_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)
歐幾里得算法是計算兩個數最大公約數的傳統算法,不管從理論仍是實際效率上都是很好地。可是卻有一個致命的缺陷,這個缺陷在素數比較小的時候通常是感覺不到的,只有在大素數時纔會顯現出來。
通常實際應用中的整數不多會超過64位(固然如今已經容許128位),對於這樣的整數,計算兩個數之間的模很簡單。對於字長爲32位的平臺,計算兩個不超過32位的整數的模,只須要一個指令週期,而計算64位如下的整數模,也不過幾個週期而已。可是對於更大的素數,這樣的計算過程就不得不禁用戶來設計,爲了計算兩個超過64位的整數的模,用戶也許不得不採用相似於多位數除法手算過程當中的試商法,這個過程不但複雜,並且消耗了不少CPU時間。對於現代密碼算法,要求計算128位以上的素數的狀況比比皆是,設計這樣的程序迫切但願可以拋棄除法和取模。
由J. Stein 1961年提出的Stein算法很好的解決了歐幾里德算法中的這個缺陷,Stein算法只有整數的移位和加減法,爲了說明Stein算法的正確性,首先必須注意到如下結論:
代碼以下:
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)
參考文獻:https://www.cnblogs.com/jason2003/p/9797750.html
https://www.cnblogs.com/Dragon5/p/6401596.html