zoukankan      html  css  js  c++  java
  • 贪心算法

    从活动选择问题引出贪心算法,然后说明贪心算法和动态规划的异同点,并说明贪心算法的求解步骤。

    活动选择问题

    【问题描述】假设有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个当中选择最大值。

    递归的方法如下:

    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种选择,这也是动态规划的特点,在发现最优子问题以后,将原问题分解为子问题以后,在诸多的子问题当中选择最优的那一个。 那么我们可不可以直接选择一个活动,那一个的结果就是最优的,这就引出了贪心算法。 在这个问题当中,我们每次选择一个结束时间最早的活动,并且证明一直做这样的选择能够让我们得到最优解,证明如下:

    image

    所以,当我们用贪心算法来求解的时候,只需要进行一个选择即可,

    递归的贪心算法如下:

    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]]

    非递归版的贪心算法如下:

    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))

    贪心算法

    贪心算法和动态规划问题一样,也需要最优子结构,原问题的最优解能够由子问题的最优解来构造。和动态规划不同的是,动态规划将原问题划分为子问题的时候涉及到诸多的选择,我们在这些选择当中选择一个最优的解,而贪心算法只涉及到一个选择:选择当前的最优解,不过贪心算法需要对这个选择进行证明,证明一直按照局部最优解进行选择,结果是全局最优解,这个性质叫做贪心选择性质。能用贪心算法解决的问题,都是可以用动态规划来解决。

    贪心算法分为以下步骤:

    1、将最优化问题转化为这样的形式:对其作出一次选择后,只剩下一个子问题需要求解。

    2、证明作出贪心选择后,原问题总是存在最优解,即贪心算法是安全的。

    3、证明作出贪心算法后,剩下的子问题满足性质:其最优解与贪心选择组合即可得到原问题的最优解,这样就得到了最优子结构。

    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章

  • 相关阅读:
    hive之external table创建
    hive之managed table创建
    Ubuntu下hadoop1.0.4安装过程
    hadoop相关Exception
    ASP.NET 数据访问类 SQLSERVER
    ASP.NET中Cookie编程的基础知识
    SourceForge上的好东西(.Net)
    ASP.NET生成高质量缩略图通用函数(c#代码)
    Sql Server实用操作小技巧集合
    分页SQL Server存储过程
  • 原文地址:https://www.cnblogs.com/jiaxin359/p/9317956.html
Copyright © 2011-2022 走看看