貪心算法
基本概念
貪心算法是指:在每一步求解的步驟中,它要求“貪婪”的選擇最佳操作,並希望通過一系列的最優選擇,能夠產生一個問題的(全局的)最優解。
貪心算法每一步必須滿足一下條件:
1、可行的:即它必須滿足問題的約束。
2、局部最優:他是當前步驟中所有可行選擇中最佳的局部選擇。
3、不可取消:即選擇一旦做出,在算法的后面步驟就不可改變了。
例題
l 求最大子數組之和問題
給定一個整數數組(數組元素有負有正),求其連續子數組之和的最大值。
def main():
s = [12,-4,32,-36,12,6,-6]
print("定義的數組為:",s)
s_max, s_sum = 0, 0
for i in range(len(s)):
s_sum += s[i]
if s_sum >= s_max:
s_max = s_sum # 不斷更新迭代s_max的值,盡可能的令其最大
elif s_sum < 0:
s_sum = 0
print("最大子數組和為:",s_max)
if __name__ == "__main__":
main()
l 最少加油次數
一輛汽車加滿油后可行駛n公里。旅途中有若干個加油站。設計一個有效算法,指出應在哪些加油站停靠加油,使沿途加油次數最少。 對於給定的n和k個加油站位置,編程計算最少加油次數。
# 設汽車加滿油后可行駛n公里,且旅途中有k個加油站
def greedy():
n = 100
k = 5
d = [50,80,39,60,40,32]
# 表示加油站之間的距離
num = 0
# 表示加油次數
for i in range(k):
if d[i] > n:
print('no solution')
# 如果距離中得到任何一個數值大於n 則無法計算
return
i, s = 0, 0
# 利用s進行迭代
while i <= k:
s += d[i]
if s >= n:
# 當局部和大於n時則局部和更新為當前距離
s = d[i]
# 貪心意在令每一次加滿油之后跑盡可能多的距離
num += 1
i += 1
print(num)
if __name__ == '__main__':
greedy()
分治算法
分治法概念
將一個復雜的問題分成兩個或更多的相同或相似的子問題,再把子問題分成更小的子問題----“分”
將最后子問題可以簡單的直接求解----“治”
將所有子問題的解合並起來就是原問題打得解----“合”
分治法特征
該問題的規模縮小到一定的程度就可以容易地解決
該問題可以分解為若干個規模較小的相同問題,即該問題具有最優子結構性質。
利用該問題分解出的子問題的解可以合並為該問題的解;
該問題所分解出的各個子問題是相互獨立的,即子問題之間不包含公共的子子問題。
第一條特征是絕大多數問題都可以滿足的,因為問題的計算復雜性一般是隨着問題規模的增加而增加;
第二條特征是應用分治法的前提它也是大多數問題可以滿足的,此特征反映了遞歸思想的應用;、
第三條特征是關鍵,能否利用分治法完全取決於問題是否具有第三條特征,如果具備了第一條和第二條特征,而不具備第三條特征,則可以考慮用貪心法或動態規划法。
第四條特征涉及到分治法的效率,如果各子問題是不獨立的則分治法要做許多不必要的工作,重復地解公共的子問題,此時雖然可用分治法,但一般用動態規划法較好。
動態規划
1、基本思想
通過把原問題分解為相對簡單的子問題的方式求解復雜問題的方法。動態規划常常適用於有重疊子問題和最優子結構性質的問題。
若要解一個給定問題,我們需要解其不同部分(即子問題),再合並子問題的解以得出原問題的解。 通常許多子問題非常相似,為此動態規划法試圖僅僅解決每個子問題一次,從而減少計算量: 一旦某個給定子問題的解已經算出,則將其記憶化存儲,以便下次需要同一個子問題解之時直接查表。 這種做法在重復子問題的數目關於輸入的規模呈指數增長時特別有用。
2、分治與動態規划
共同點:二者都要求原問題具有最優子結構性質,都是將原問題分而治之,分解成若干個規模較小(小到很容易解決的程序)的子問題.然后將子問題的解合並,形成原問題的解.
不同點:分治法將分解后的子問題看成相互獨立的,通過用遞歸來做。
動態規划將分解后的子問題理解為相互間有聯系,有重疊部分,需要記憶,通常用迭代來做。
3、問題特征
最優子結構:當問題的最優解包含了其子問題的最優解時,稱該問題具有最優子結構性質。
重疊子問題:在用遞歸算法自頂向下解問題時,每次產生的子問題並不總是新問題,有些子問題被反復計算多次。動態規划算法正是利用了這種子問題的重疊性質,對每一個子問題只解一次,而后將其解保存在一個表格中,在以后盡可能多地利用這些子問題的解。
4、步驟
描述最優解的結構
遞歸定義最優解的值
按自底向上的方式計算最優解的值
由計算出的結果構造一個最優解
