貪心算法
百度的定義: 貪心算法(又稱貪婪算法)是指,在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優上加以考慮,他所做出的是在某種意義上的局部最優解。
通俗一點講,當要解決某一個問題時,先判斷第一步的最優解,然後把剩下的步驟看作下一個遞歸的具體問題。
例如0-1揹包問題:給定n種物品和一個揹包。物品i的重量是Wi,其價值爲Vi,揹包的容量爲C。應如何選擇裝入揹包的物品,使得裝入揹包中的物品的總價值最大?
假設具體問題數值:A物品,重量爲6kg,價值爲8元,
B物品,重量爲8kg,價值爲13元,
C物品,重量爲10kg,價值爲15元
揹包可以裝爲50kg的物品。
有經驗的小朋友肯定首先判斷拿取哪一個物品既輕又有價值。A物品:單位重量的價值爲8/6(元)
B物品:單位重量的價值爲13/8(元)
C物品:單位重量的價值爲15/10(元)
計算看出,只要能裝,首先要拿B物品,因爲單位價值最高。
即剩餘50kg
第一步:我有 50 kg揹包,我可以選擇的物品有 A,B,C
第二步:選擇當前這一步最優解,即 B。而問題的揹包變成50-8=42kg,而問題只有揹包重量改變,重新回到第一步判斷。
所以就是:(選擇優先B,然後C,然後A)
1:我有 50kg 揹包,我可以選擇A,B,C,總價值爲0 ,我選擇B
2:我有 42kg 揹包,我可以選擇A,B,C,總價值爲13 ,我選擇B
3:我有 34kg 揹包,我可以選擇A,B,C,總價值爲26 ,我選擇B
4:我有 26kg 揹包,我可以選擇A,B,C,總價值爲39 ,我選擇B
5:我有 18kg 揹包,我可以選擇A,B,C,總價值爲52 ,我選擇B
7:我有 2kg 揹包,我可以選擇 ,總價值爲78 ,沒有空間選了
這時候有眼尖的小朋友就問了:“那這道題這樣做的話,只能取6個B物品,重量爲48kg,總價值爲6*13=78元。那如果我取5個B物品,1個C物品,不是剛好50kg嗎?這樣總價值有5*13+15=80元呢”。其實這就看出,貪心算法得到的並不是最優解。
如何用代碼實現呢?
# coding=utf-8
if __name__ == '__main__':
beg = 50 #揹包50kg
value = 0 #已經獲得的價值
choice = []
while beg > 0: #如果揹包還有空位,則遞歸
if beg >= 8: #選擇當前這一步的最優解,既選擇B商品
beg = beg - 8
value = value + 13
choice.append("B")
elif beg >= 10: #要是B商品選擇不了,則選擇第二單位價值的物品,即A物品
beg = beg - 10
value = value + 15
choice.append("A")
elif beg >= 6:
beg = beg - 6
value = value + 8
choice.append("C")
else: #當所有的物品都選擇不了,則退出
break
print "剩餘的揹包重量:",beg
print "獲得的總價值:",value
print "選擇的物品的類型及順序:",choice
貪心算法只能保證次優解。
動態規劃
1:最優子結構
2:子問題重疊
3:邊界
邊界體現在揹包沒有空間,或者金礦沒有足夠人數取挖。
4:子問題獨立
看完這裏還是迷迷糊糊?不急,還沒說完,上面那四個點只是基礎,還需要剔除重複的步驟。(相當於深搜的剪枝吧..)
這裏我再引入一道題:斐波那契數列之青蛙跳臺階。
一隻青蛙一次可以跳上 1 級臺階,也可以跳上2 級。求該青蛙跳上一個n 級的臺階總共有多少種跳法。
這道題其實用動態規劃可以解決。一般的想法:
# coding=utf-8
def fib(n): #當前有N個臺階,可以選擇跳一個臺階,也可以選擇跳兩個
if n <=1 : #邊界問題,要是當前只剩下一個臺階,則只剩下一個方法跳。
return 1
else: #跳一個臺階和跳兩個臺階都是一個選項。
return fib(n-1)+fib(n-2)
print fib(5)
這種最簡單的方法是可以實現少臺階的情況,一旦多情況(100個臺階)就運行不下去。http://blog.csdn.net/baidu_28312631/article/details/47418773博客作者闡述瞭如果動態規劃不優化的話,答案可能性有多少。在這裏我用圖列出一共有多少種可能性。每到葉結點的0代表剩下0步,確定唯一的跳臺階的唯一確定方案。上面一共有8個葉結點,即有8種方案。不過這裏我們不是討論這道題的解,而是討論動態規劃的優化。
當我剩下1層臺階時,我只有兩種可能,就是這裏(或者從2到0)
咦,那當我只剩下2層臺階時,我也只有一種可能性,就是
當我剩下3層臺階時,也只有
剩下4層臺階時,有
這樣的話,其實我有5層臺階時,其中包括1個(4層臺階子樹),2個(3層臺階子樹),3個(2層臺階子樹),5個(從1到0),4個(從2直接到0)。
class Dp1(object): #動態規劃類
def __init__(self,n): #初始化
self.mark = [0 for _ in xrange(n+1)] #定義一個一維數組,初始化全部爲0,長度爲臺階數。用來當作“備忘錄”。
print self.dp(n) #開始遞歸
def dp(self,n): #遞歸的方法
self.m = 0 #m的含義是當前n個臺階有m種跳法
if self.mark[n] != 0: #先從備忘錄尋找n,若存在mark[n]不等於0,則代表曾經計算過,n個臺階有mark[n]種跳法
self.m = self.mark[n] #若備忘錄有,則直接得到n層臺階的答案
elif n <= 0: #從這裏開始的四行是用來判斷“邊界問題”
if n == 0: #若剛好跳完臺階,則這樣算一種方法
self.m = 1 #m變成1,代表是一種可行方法
else: #有可能跳的臺階超過實際臺階數
self.m = 0 #m爲0,代表不可行
elif n>0: #這裏兩行是用於規劃轉移方程式(其實這裏很簡單),青蛙只有兩種可能,跳一層或者跳兩層。
self.m = self.dp(n-2)+self.dp(n-1) #當前n層臺階的解個數 等於 n-1層臺階的解 + n-2層臺階的解
self.mark[n] = self.m #把m放入備忘錄,下次若是再次是n層臺階,則不用計算直接取備忘錄的數。(優化)
return self.m #返回
if __name__ == '__main__':
dp1 = Dp1(100)
1、構造問題所對應的過程。
2、思考過程的最後一個步驟,看看有哪些選擇情況。
3、找到最後一步的子問題,確保符合“子問題重疊”,把子問題中不相同的地方設置爲參數。
4、使得子問題符合“最優子結構”。
5、找到邊界,考慮邊界的各種處理方式。
6、確保滿足“子問題獨立”,一般而言,如果我們是在多個子問題中選擇一個作爲實施方案,而不會同時實施多個方案,那麼子問題就是獨立的。
7、考慮如何做備忘錄。
8、分析所需時間是否滿足要求。
9、寫出轉移方程式。
class Dp(object):
def __init__(self, n, m, peopleneed, gold): #n是總人數,m是金礦數
self.peopleneed = peopleneed #每一個金礦開挖需要的人數
self.gold = gold #每一個金礦的金礦數
self.maxgold =[[-1 for i in xrange(n)] for i in xrange(m)] #初始化備忘錄,創建一個m行n列的二維數組。
print self.getmaxgold(n,m) #n,m減一是因爲數組是從0開始
def getmaxgold(self,n,m):
#retmaxgold = 0
if self.maxgold[m-1][n-1] != -1:
retmaxgold = self.maxgold[m-1][n-1]
elif m == 0:
if (n >= self.peopleneed[m-1]):
retmaxgold = self.gold[m-1]
else:
retmaxgold = 0
elif n >= self.peopleneed[m-1]:
retmaxgold = max(self.getmaxgold(n - self.peopleneed[m-1],m -1)+self.gold[m-1],self.getmaxgold(n,m-1))
else:
retmaxgold = self.getmaxgold(n,m-1)
self.maxgold[m-1][n-1] = retmaxgold
return retmaxgold
if __name__ == '__main__':
peopleneed = []
gold = []
str = raw_input("")
try:
str = str.split(" ")
n = int(str[0])
m = int(str[1])
for i in range(m):
goldmount = raw_input("")
goldmount = goldmount.split(" ")
peopleneed.append(int(goldmount[0]))
gold.append(int(goldmount[1]))
except:
print "輸入格式錯誤"
dp = Dp(n, m, peopleneed, gold)
100 5
77 92
22 22
29 87
50 46
99 90
答案: 133
下面找了一些題目練習(動態規劃只有練習才能提高...只能不斷練習)
例題來源:http://www.cnblogs.com/wuyuegb2312/p/3281264.html#q1
1.硬幣找零
難度評級:★
假設有幾種硬幣,如1、3、5,並且數量無限。請找出能夠組成某個數目的找零所使用最少的硬幣數。
# coding=utf-8
class Dp2(object):
def __init__(self,money):
self.mark = [0 for _ in xrange(money+1)] #備忘錄
print self.dp(money) #開始遞歸
def dp(self,money):
self.coin = 0 #需要的硬幣數爲0
if self.mark[money] != 0: #在備忘錄中尋找該金額下的最少硬幣找零數,若存在,則取出
self.coin = self.mark[money]
elif money <= 0: #邊界問題
if money == 0: #如果金額爲零,則代表剛好算是一種找零方法
self.coin = 0 #這裏的0不是代表硬幣數爲0,而是代表這種方法可行,因爲在下面已經有加1,若是這裏coin爲1,結果就會比答案多1
else:
self.coin = float("inf") #若是金額爲負數,即“拿多了”,這種方法不可行,則硬幣消耗數爲 無窮大
elif money > 0:
self.coin = min(self.dp(money-1),self.dp(money-3),self.dp(money-5))+1 #遞歸,找出最少的可以湊齊金額數money的方法
self.mark[money] = self.coin #做備忘錄
return self.coin
if __name__ == '__main__':
dp2 = Dp2(65) #找零錢
之後慢慢再更新題目