贪心算法

从活动选择问题引出贪心算法,而后说明贪心算法和动态规划的异同点,并说明贪心算法的求解步骤。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章

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息