從活動選擇問題引出貪心算法,而後說明貪心算法和動態規劃的異同點,並說明貪心算法的求解步驟。html
【問題描述】假設有n個活動的組合 $S = \{ a_1,a_2, ...,a_n \}$,這些活動使用同一個教室,同一時刻,教室只能舉行一個活動,每一個活動$a_i$都有一個開始時間$s_i$和一個結束時間$f_i$, 其中 $0 \leq s_i < f_i $,若活動被選中,則發生在區間$[s_i, f_i)$內。 假如兩個活動$a_i, a_j$,知足$[s_i, f_i)$和$[s_j, f_j)$不重疊,則它們是兼容的。 活動選擇問題是,在這些活動當中選擇一個最大的兼容活動集,它是活動的數量最多的集合。算法
尋找這個問題的最優子結構,原問題是尋找事件S中的最多的活動,令$s_{min}$表示S中最先開始的活動, $f_{max}$是最晚結束的活動,原問題等價於選擇時間$[s_{min}, f_{max}]$內的活動。 咱們假設活動$a_i$是最優解當中的一個值,它的時間爲$[s_i, f_i)$,那麼當咱們選擇$a_i$之後,會出現兩個子問題,選擇時間$[s_{min}, s_i]$ 和時間$[f_i, f_{max}]$的活動。這樣就能夠創建遞歸方程,使用動態規劃的思想來進行求解。而這樣的$a_i$有n個,在這n個當中選擇最大值。安全
遞歸的方法以下:app
def activity_selection(s, f, start_time, end_time): """首先是作一個選擇,而且假設該選擇有最大值,在給選擇下產生子問題""" if start_time == end_time: return 0 max_value = 0 for i in range(len(s)): current_start = s[i] current_end = f[i] if current_start >= start_time and current_end <= end_time: max_value = max(max_value, activity_selection(s, f, start_time, current_start)+activity_selection(s, f, current_end, end_time) + 1) return max_value if __name__ == '__main__': # s是活動開始的時間,f是活動結束的時間,按照活動結束的時間進行了排序。 s = [1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12, 14] f = [4, 5, 6, 7, 9, 9, 10, 11, 12, 14, 16, 20] start_time = min(s) end_time = max(f) print(activity_selection(s, f, start_time, end_time))
動態規劃方法以下:優化
def activity_selection(s, f, start_time, end_time): total_time = end_time - start_time + 1 memorized = [[0 for i in range(total_time+1)] for i in range(total_time+1)] for i in range(1, total_time+1): for j in range(1, total_time+1): max_value = 0 for index in range(len(s)): current_start = s[index] - start_time current_end = f[index] - start_time if current_start-start_time >= i and current_start-start_time <=j and current_end >=i and current_end <= j: max_value = max(max_value, memorized[i][current_start] + memorized[current_end][j] + 1) memorized[i][j] = max_value return memorized[1][-1]
在這個問題當中,咱們在選擇一個最優的活動的時候,有n種選擇,這也是動態規劃的特色,在發現最優子問題之後,將原問題分解爲子問題之後,在諸多的子問題當中選擇最優的那一個。 那麼咱們可不能夠直接選擇一個活動,那一個的結果就是最優的,這就引出了貪心算法。 在這個問題當中,咱們每次選擇一個結束時間最先的活動,而且證實一直作這樣的選擇可以讓咱們獲得最優解,證實以下:spa
因此,當咱們用貪心算法來求解的時候,只須要進行一個選擇便可,3d
遞歸的貪心算法以下:code
def recursive_activity_selector(s, f, k, n): activity = [] # 活動m的開始時間應該小於活動k的結束時間 m = k + 1 while m <= n and s[m] < f[k]: m += 1 if m <= n: return [[s[m],f[m]]]+ recursive_activity_selector(s, f, m, n) else: return activity if __name__ == '__main__': s = [1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12, 14] f = [4, 5, 6, 7, 9, 9, 10, 11, 12, 14, 16, 20] # 插入0,表示前面有一個活動是從0開始,方便後續運算 s.insert(0, 0) f.insert(0, 0) m = 0 n = len(s) print(recursive_activity_selector(s, f, 0, n-1)) # result [[1, 4], [5, 7], [8, 11], [12, 16]]
非遞歸版的貪心算法以下:htm
def greedy_activity_selector(s, f): """選擇結束時間最先的活動""" activity = [[s[0], f[0]]] pre_activity = 0 for index in range(1, len(s)): if s[index] >= f[pre_activity]: pre_activity = index activity.append([s[index], f[index]]) return activity if __name__ == '__main__': s = [1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12, 14] f = [4, 5, 6, 7, 9, 9, 10, 11, 12, 14, 16, 20] print(greedy_activity_selector(s, f))
貪心算法和動態規劃問題同樣,也須要最優子結構,原問題的最優解可以由子問題的最優解來構造。和動態規劃不一樣的是,動態規劃將原問題劃分爲子問題的時候涉及到諸多的選擇,咱們在這些選擇當中選擇一個最優的解,而貪心算法只涉及到一個選擇:選擇當前的最優解,不過貪心算法須要對這個選擇進行證實,證實一直按照局部最優解進行選擇,結果是全局最優解,這個性質叫作貪心選擇性質。能用貪心算法解決的問題,都是能夠用動態規劃來解決。blog
貪心算法分爲如下步驟:
一、將最優化問題轉化爲這樣的形式:對其做出一次選擇後,只剩下一個子問題須要求解。
二、證實做出貪心選擇後,原問題老是存在最優解,即貪心算法是安全的。
三、證實做出貪心算法後,剩下的子問題知足性質:其最優解與貪心選擇組合便可獲得原問題的最優解,這樣就獲得了最優子結構。
0-1揹包問題的求解能夠參考個人博客:揹包問題 。 分數揹包問題和0-1揹包問題很像,可是物品的重量是能夠分的,在選擇拿物品的時候能夠拿走一部分,而沒必要是整個物品。 分數揹包問題能夠用貪心算法來求解,可是0-1揹包問題卻不能用貪心算法來求解。分數揹包問題的求解以下:
def fraction_package(w, v, m): """ 分數揹包問題, 使用貪心算法來求解 Args: w: weight v: value m: max weight of blg """ current_weight = 0 max_value = 0 veight_per_weight = [] for i in range(len(w)): veight_per_weight.append(v[i]/w[i]) # 按照物品的單位價值,從大到小對index進行排序。 value_per_weight_index = sorted(range(len(veight_per_weight)), key = lambda x:veight_per_weight[x], reverse=True) # 每次選擇,老是選擇單位價值最高的物品 for index in value_per_weight_index: if current_weight < m: remainder_weight = m - current_weight if remainder_weight > w[index]: current_weight += w[index] max_value += v[index] else: current_weight += remainder_weight max_value += veight_per_weight[index] * remainder_weight return max_value if __name__ == '__main__': w = [10, 20, 30] v = [60, 100, 120] m = 50 print(fraction_package(w, v, m))
參考:
算法導論16章