貪心算法

從活動選擇問題引出貪心算法,而後說明貪心算法和動態規劃的異同點,並說明貪心算法的求解步驟。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

image

因此,當咱們用貪心算法來求解的時候,只須要進行一個選擇便可,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揹包問題很像,可是物品的重量是能夠分的,在選擇拿物品的時候能夠拿走一部分,而沒必要是整個物品。 分數揹包問題能夠用貪心算法來求解,可是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章

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息