算法导论之贪心算法(Huffman编码和拟阵)

贪心算法,在解决最优化问题上,通过得到子问题的局部最优解来合成问题的一个解,以局部最优选择来输出一个全局最优解。

问题要用贪心算法来求解,需满足和动态规划一样的最优子结构特征,同时还需要再每个子问题最优解选择上具有贪心性质。所谓贪心性质,就是本来一个问题分成两个子问题求解,但现在只需要选择一个来求解,而这个子问题的局部最优就是全局最优。贪心了,只要解决一个子问题就解决了整个问题。

通过活动选择问题来说明如何通过动态规划解转化为贪心解。活动选择问题在现实中有很多实际应用场合,记得在大学时,各类学生社团经常要共用某一类公共场合来举办活动或会议,这个时候就要协调出合理的安排,最大化满足社团对资源的需求。用数学模型来描述,就是n个活动集合{a1,a2,…,an}要排斥使用公共资源,如果以活动开始时间和结束时间作为占用资源的标志量,那么就是要求解这个集合中最大子集可以最大化使用这个资源。活动选择问题就是要选择出一个由互相兼容的问题组成的最大子集合。

针对活动选择问题,按照动态规划解决的第一步就是寻找最优子结构。假设Sij为活动的子集,ak为其中一个活动,sk和fk分别为活动的开始时间和结束时间,数学模型描述如下:


这个发现就是说,最早结束时间的活动am使两个子问题求解变成一个子问题求解,是为贪心。

正是因为活动选择问题中有最早结束时间的活动使问题可以通过贪心策略来求解。具有贪心性质的问题,在子结构求解上,使求解子问题的数量减少一半,并能采用自顶向下来解决子问题,而不需如动态规划那般自底向上。

看下活动选择问题在定义最优子结构和递归解的动态规划法之后如何通过贪心、自顶向下算法求解。

输入数组的开始时间s和结束时间f,以及下标i和n,函数如下:

Fun_recursive_activity_selector(s,f,i,n){//初始i=0

    m=i+1;

    while m=mi

        do m=m+1;

    if m=

         then return { am }UFun_recursive_activity_selector(s,f,m,n)

    else return 空集

}

自底向上和自顶向下要说区别,在递归上很明显就体现为,自底向上是先递归而后求解,而自顶向下则是先求解而后递归。

当然贪心算法这个递归也是可以转化为迭代来运算,不需要通过递归,其算法时间性能是线性级。

贪心算法通过一系列的选择来给出某一问题的最优解,虽然这种启发式的策略并不是总能找到最优解,但却是解决问题的一个思路。和动态规划通过组合子问题的最优解来获取问题的最优解不一样,贪心算法是通过选择问题的最优解来选择子问题再进行最优求解。贪心算法的一般过程是:

第一:定义问题的最优子结构;

第二:设计一个递归解;

第三:证明在递归的任何一个阶段,有贪心的最优选择;

第四:证明通过贪心选择后,所有子问题都为空,除一个外,这个就是贪心选择后具有最优解的子问题;

第五:设计一个实现贪心策略的递归算法;

第六:将递归算法转化为迭代算法。

需要在说明的是贪心算法必须具有贪心选择的性质,一个全局最优解可以通过局部最优(贪心)选择来达到。在动态规划中,每一步都要依赖子问题的解做选择,所以用自底向上,从小子问题处理至大子问题。而贪心算法,先选择看似最佳的子问题,再对子问题进行求解,所以采用自顶向下,一个个向下做贪心选择。或者说,动态规划是先求解而后选择,而贪心算法是先选择而后求解,这样大量缩小子问题数量,当然不见得所做选择就是最佳。当然贪心算法和动态规划一样,要解决的问题都必须是具有最优子结构的特点。对一个问题来说,如果它的一个最优解包含了其子问题的最优解,则称该问题具有最优子结构。

算法导论中用1-0背包问题和部分背包问题来说明动态规划和贪心算法对问题的适用性。1-0背包问题是1和0的选择,就是要不带走物品,要不留下,只能选择其一,而不能带走物品的一部分,而部分背包问题是可以把带走物品的一部分,而不必做出1-0的二分选择。两种背包问题都具有子结构性质,但只有部分背包问题可以用贪心算法来解决。因为部分背后问题中,对每件物品进行价值计算,可以选择最高价值的先拿,在选择次高价值的物品带走。

在对贪心算法进行基础的认识后,下面看赫夫曼编码,赫夫曼编码具有根据字符出现频度较小的贪心选择性质,也具有二叉树的最优子结构性质,是贪心算法的典型案例。

赫夫曼编码是一种非常有效的数据压缩技术。赫夫曼贪心算法维护一张字符出现频度表,据此构造将每个字符表示成二进制串的最优方式。涉及两个概念,一个是无前缀编码(prefix-free code),即编码是独立的,互相不构成前缀;另一个是可变长编码,即对频度高的字符赋以短编码,而对频度对的字符则赋以较长编码,比固定长度编码能压缩空间。

赫夫曼编码采用二叉树表示,叶子为字符,每个字符编码为从根到叶子节点的路径,0转左,1转右。最优编码是一棵满二叉树,树种每个非叶节点都有两个子节点。

假定文件采用无前缀编码的二叉树T,很容易计算出编码文件所需的位数。字母表C中每一个字符为c,设f(c)表示c在文件中出现的频度,d(c)表示c的叶子在树种的深度(也即字符c的编码长度),可得编码一个文件所需的位数就是:


基于上述的说法,通过贪心算法构造赫夫曼编码二叉树的过程,主要是对最优子结构(字符频度越小,在树中的深度越高,编码长度越长)进行贪心选择,贪心选择就是识别并合并两个频度最低的对象,两个对象合并的结果是一个新对象,其频度为被合并两个对象的频度之和,用于和其他对象在进行频度比较。

具体算法就是不断识别最低频度并合并,导论中还证明了最优无前缀编码的问题具有贪心选择和最优子结构性质。证明逻辑,在不失一般性下,构造另一棵树来等同最优。

总结下,通过赫夫曼编码来进行数据二进制串表示是一种最优方式,赫夫曼编码满足贪心算法的最优子结构和贪心选择两个性质,构造出满二叉树来表示字符编码。赫夫曼编码是一个过程,是根据字符出现频度构造满二叉树的过程,这个构造过程符合贪心算法的两个性质。

满足最优子结构和贪心选择两个性质的问题最优求解,可以通过贪心算法来解决,但贪心算法如何才能做出最优解从而使贪心选择是正确的,导论中给出了拟阵作为其理论基础。

拟阵的定义,及矩阵、图的拟阵,实际是集合的基础理论,寻找最大独立子集。拟阵具有贪心选择性质,具有最优子结构性质。对于拟阵需要专题学习研究,尤其是图的拟阵。这里先认识到贪心算法的理论基础是拟阵。导论中还给出任务调度问题采用贪心算法来解决的思路。

你可能感兴趣的:(Algorithm,算法导论专栏)