zoukankan      html  css  js  c++  java
  • 跟我一起学算法——贪心算法

    @


    贪心算法(Greedy Algorithms)

    思想

    每次选择总是选出当前最优的解
    特点 :只计算部分子问题,而动态规划需要计算全部子问题,但同样依赖最优子结构性质。
    步骤 :先排个序,再按每次最优循环选择。

    条件

    1. 最优子结构性质
      问题的整体最优解中包含子问题的最优解
    2. 贪心选择性质:局部最优=>全局最优
      本次贪心选择与剩余子问题的最优解可以构成原问题的最优解。

    贪心算法正确性的证明()

    包括两部分:贪心选择性质的证明和最优子结构证明

    贪心选择的证明,方法一:

    1. 给出贪心算法A的描述
    2. 反证:先给出一个贪心算的最优解A,然后假设非贪心选择的B是最优解,B可由A序列中调换元素
      得到,证明B的解值比A差。

    贪心选择的证明,方法二——剪枝法:

    1. 贪心算法A的描述
    2. 假设O是一个最优算法
    3. 找出O和A中的一个不同
    4. Exchange这个不同的东西,然后讨论得到的算法O'不比O差
    5. 若这种不同共有n个,然后我exchange n次就可以消除所有不同,得到A,且算法的质量不比O差。
      这说明A as good as O,所以A是最优的。如huffman树贪心选择性质证明

    最优子结构证明:

    1. 给出最优解A和子问题最优解B
    2. 假设B不是最优,C才是最优
    3. 证明将C替换B后的得到的A'优于A,矛盾。

    应用:活动选择问题

    也可以看做时间片段集合问题。给一些时间片段集合T={(a1,b1)(a2,b2)...(an,bn)},
    找出一个元素个数最多的子集S,子集中的每个元素的时间片段没有交叉。O(n)

    先把活动按结束时间排序,然后每次找上一次活动结束后可以开始的第一个活动。

    贪心选择的证明

    https://blog.csdn.net/rookie_ly/article/details/79039295

    应用:Huffman编码

    算法

    使用最优前缀码,每次挑选二个频度值最小的字符,作为孩子节点,父结点的频度值为孩子的频度和。
    haffman树的叶节点是满的。

    def Huffman(charList):
        while len(charList)!=1:
            d=Node()
            x=min(charList)
            d.left=x
            charList.del(x)
            y=min(charList)
            d.right=y
            charList.del(y)
            d.val=d.left.val+d.right.val
            charList.append(d)
        return charList[0] #根结点
    

    时间复杂度O(nlgn)

    证明贪心选择性质

    令C为一字符集,C中每个字符c∈C的频度为f(c),x,y∈C是二个具有最小频度值的字符,则必定
    存在C的一种最优前缀码,使x,y的码长相同且仅最后一位不同。

    1. 最优前缀码树是一棵叶结点满二叉树
    2. 令T是最优前缀码树,证明x,y码长相同且仅最后一位不同

    证明最优子结构性质

    若在C中去除字符x,y,加上频度为f[z]=f[x]+f[y]的新字符z,则构成一新的字符集
    C’=C-{x,y}∪{z}。令T’是C’的一棵最优前缀码树,那么当用孩子结点为x,y的内部结点替换C’
    中的叶结点z后得到的新树T是一棵最优前缀码树。

    反证法

    用huffman编码斐波那契数

    a:1 11111110
    b:1 1111110
    c:2 111110
    d:3 11110
    e:5 1110
    f:8 110
    g:13 10
    h:21 0
    可推广到n。

    应用:最小生成树( Kruskal Algorithm for Minimum Spanning Tree)

    对于边集合E,先按每个边的cost排序,从小到大,然后按如下方法构造一个MST,每次选cost最小
    的边加入MST,如果这条边使MST出现环路,则舍弃,然后重复上面的操作。

    贪心算法正确性证明

    1. 图为G(V,E),假设上述算法A的MST为T2,有 最优算法O的MST为T1,T2 != T1.即至少存在一条边e,
      它在T1,不在T2。e把T1分为两个集合Ta和Tb,且Ta中的点+Tb中的点=V。则在T2中一定存在一条边w(w!=e),
      w的一个v在Ta中,另一个在Tb中。由A的定义知,选了w而没选e,说明 cost(w)<cost(e)
    2. 构造一棵新的树T3= E(T1) - e + w,显然T3是一个生成树,且cost(T3)<=cost(T1),则T3是MST。
    3. T3比T1更接近T2,若T1和T2有n条不同的边,则按上述方法经过n次变化,可以得到T2,同时cost<=T1,
      则T2是MST。

    删数问题

    贪心策略

    从高到低位搜索,若各位数字递增,则删除最后一个数字,窦泽删除第一个递减区间的首字符(换句
    话说:删除递增区间的最后一个数字),然后回到首位继续搜索。

    贪心选择正确性证明

    T1 = a_1*10^(n-1) + a_2*10^(n-2)+ ...+a_(n-1)*10+a_n

    1. 若各位数单调递增,则删除最后一个数:
      T2 = a_1*10^(n-2) + a_2*10^(n-3)+ ...+a_(n-2)*10+a_(n-1)
      若删除的另一个数a_k,则
      T2'=a_1*10(n-2)+a_2*10(n-3)+a_(k-1)*10(n-k)+a_(k+1)*10(n-k-1)+...+
      a_(n-1)*10+a_n
      显然,T2-T2'=(a_k-a_(k+1)*10^(n-k-1) + (a_(k+1)-a_(k+2))*10^(n-q-2)+...+
      (a_(n-2)-a_(n-1))*10+(a_(n-1)-a_n) <= 0
      所以贪心选择是正确的。
    2. 若存在递减,则删除递减序列第一个数:

    其他应用

    部分背包问题
    找硬币问题
    prime算法和迪杰斯特拉算法

    胚(matroids)

    定义

    胚是满足以下条件的有序对M=(S,I):

    1. S有穷非空集
    2. I的遗传性:I是S的非空独立子集族,使得若B∈I,且A是B的子集,则A∈I。即B独立,B的子集亦独立。
    3. M的交换性:若A∈I,B∈I且|A|<|B|,则存在一个元素x∈B-A,使得A∪{x} ∈I。
      例如,无向图G=(V,E),SG=E为G中的边集,IG为SG的无回路边集族,MG=(SG,IG)是一个胚。

    最大独立子集

    给定一个胚M=(S,I),对于I中一个独立子集A∈I,若S中有一个元素x不属于A,使得将x加入A后仍保
    持A的独立性,即A∪{x}∈I,则称x为A的一个可扩张元素(extension)。
    当胚中的一个独立子集A没有可扩张元素时,称A是一个最大独立子集。
    胚中所有最大独立子集大小相等。

    加权胚和最优子集

    权值最大的独立子集称为最优子集。
    通用贪心算法:按权值递减排序,循环取权值最大的+独立性检测。O(nlgn)

  • 相关阅读:
    一致性哈希算法
    Discourse 的标签(Tag)只能是小写的原因
    JIRA 链接 bitbucket 提示错误 Invalid OAuth credentials
    JIRA 如何连接到云平台的 bitbucket
    Apache Druid 能够支持即席查询
    如何在 Discourse 中配置使用 GitHub 登录和创建用户
    Apache Druid 是什么
    Xshell 如何导入 PuTTYgen 生成的 key
    windows下配置Nginx支持php
    laravel连接数据库提示mysql_connect() :Connection refused...
  • 原文地址:https://www.cnblogs.com/chzhyang/p/12402501.html
Copyright © 2011-2022 走看看