美文网首页
哈夫曼树与哈夫曼编码、集合

哈夫曼树与哈夫曼编码、集合

作者: Spicy_Crayfish | 来源:发表于2016-07-03 23:03 被阅读490次

    什么是哈夫曼树(Huffman Tree)
    eg:将百分制的考试成绩转换为五分制的成绩
    if ( score < 60 ) grade = 1;
    else if ( score < 70 ) grade = 2;
    else if ( score < 80 ) grade = 3;
    else if ( score < 90 ) grade = 4;
    else grade = 5;
    建立判定树,如果考虑到学生成绩的分布概率,我们发现得3分和得4分的同学占了绝大多数的比例,直接用上述判别流程建立判定树 显然效率不够高。
    修改判定树:
    if ( score < 80 )
    {
    if ( score < 70 )
    if ( score < 60 ) grade = 1 ;
    else grade = 2 ;
    } else if ( score < 90 ) grade = 4 ;
    else grade = 5;
    按照这种判别流程建立的判定树 效率得到显著提高。
    如何根据结点不同的查找频率 构造更有效的搜索树?

    -哈夫曼树的定义

    带权路径长度(WPL):设二叉树有n个叶子结点,每个叶子结点带有权值(即频率)Wk,从根结点到每个叶子结点的长度为Lk,则每个叶子结点的带权路径长度之和为 WPL=W1L1+W2L2+ …… +Wn*Ln
    目标:将WPL降到最低。最优二叉树或哈夫曼树就是WPL最小的二叉树

    -哈夫曼树的构造:

    每次把权值最小的两棵二叉树合并,得到的新结点的权值为两棵树的权值的和,再由新结点与剩下的结点中再挑出两个权值最小的树 再次合并,直到最后所有树连接在一起
    如何选取两个最小的树?利用堆
    typedef struct TreeNode *HuffmanTree ;
    struct TreeNode {
    int Weight ;
    HuffmanTree Left, Right ;
    }
    HuffmanTree Huffman ( MinHeap H )
    {
    // 假设 H -> Size 个权值已经存在 H -> Element [ ]-> Weight 里
    int i ; HuffmanTree T ;
    BuildMinHeap ( H ) ; // 将 H -> Element [ ]按权值调整为最小堆
    for ( i = 1 ; i < H -> Size ; i ++ ) { // 做 H -> Size - 1 次合并
    T = malloc ( sizeof ( struct TreeNode ) ) ; // 建立新结点
    // 从最小堆中删除一个结点 作为新树的左子结点
    T -> Left = DeleteMin ( H ) ;
    // 从最小堆中删除一个结点 作为新树的右子结点
    T -> Right = DeleteMin ( H ) ;
    // 计算新权值
    T -> Weight = T -> Left -> Weight + T -> Right -> Weight ;
    Insert ( H , T ) ; // 将新树插入最小堆
    }
    T = DeleteMin ( H ) ;
    return T ;
    }
    整体复杂度为 O( N logN )

    -哈夫曼树的特点:

    没有度为1的结点
    n个叶结点的哈夫曼树共有2n-1个结点: n0:叶结点总数,n1:只有一个儿子的结点总数,n2:有两个儿子的结点总数; 可知:n2=n0-1;
    哈夫曼树的任意非叶结点的左右子树交换后仍是哈夫曼树;
    对同一组权值,存在不同构的两棵哈夫曼树,不过WPL值是一样的

    -哈夫曼编码

    给定一段字符串,如何对字符串进行编码,可以使得该字符串的编码存储空间最少?
    假设有一段文本 包含58个字符,并且由以下7个字符构成:a,e,i,s,t,空格(sp),换行(nl);这7个字符出现的次数不同。如何对这7个字符进行编码 使得总编码空间最少?
    分析:
    (1)用等长ASCII编码:58 x 8=464位;
    (2)用等长3位编码:58 x 3=174位;(因为只有7个字符,3位编码可编8个字符)
    (3)不等长编码:出现频率高的字符用的编码短些,出现频率低的字符则可以编码长些
    不等长编码会长生二义性,如a=1,e=0,s=11,t=10;那么编码1011就会有多种不同意思,因为我们不知道各个编码长度具体是多少。
    前缀码(prefix code):任何字符的编码都不是另一字符编码的前缀,可以无二义地解码
    用二叉树进行编码:(1)左右分支:0、1 (2)字符只在叶结点上
    只要待编字符在叶结点上,其二叉树编码都不是另一字符编码的前缀
    由哈夫曼树构造一棵编码代价最小的树
    例:

    002WV0d1zy70O2MDV1H55&690.png

    -集合的表示:

    集合运算:交集、并集、补集、差集,判定一个元素是否属于某一集合
    并查集:集合并、查某元素属于什么集合
    eg:10台电脑{1,2,3,…,9,10}已知下列电脑之间已经实现了连接:1和2、2和4、3和5、4和7、5和8、6和9、6和10,问2和7之间、5和9之间是否是连通的
    思路:(1)将10台电脑看成10个集合(2)已知一种连接“x和y”,就将x和y对应的集合合并(3)查询“x和y是否是连通的”就是判别x和y是否属于同一集合;这种思路就是并查集。
    并查集问题中集合存储的实现:
    可以用树结构表示集合,树的每一个结点代表一个集合元素;双亲表示法:孩子指向双亲
    采用数组存储形式:data表示元素的值,Parent表示其父结点的下标,根结点的Parent用-1表示;
    数组中每个元素的类型描述为:
    typedef struct {
    ElementType Data ;
    int Parent ; // 每个结点的父结点的下标
    } SetType ;

    -集合运算

    (1)查找某个元素所在的集合(用根结点表示)

    int Find ( SetType S [ ], ElementType X )
    { // 在数组 S 中查找值为 X 的元素所属的集合
    // MaxSize 是全局变量,为数组 S 的最大长度
    int i ;
    for ( i = 0 ; i < MaxSize && S [ i ]. Data != X ; i ++ ) ;
    if ( i >= MaxSize ) return -1 ; // 未找到X,返回-1
    for ( ; S [ i ] . Parent >= 0 ; i = S [ i ] . Parent ) ;
    return i ; // 找到X所属集合,返回树根结点在数组S中的下标
    }

    (2)集合的并运算

    分别找到X1和X2两个元素所在集合树的根结点,如果它们不同根 则将其中一个根结点的父结点指针设置成另一个根结点的数组下标。
    void Union ( SetType S[ ], ElementType X1 , ElementType X2 )
    {
    int Root1 , Root2 ;
    Root1 = Find ( S , X1 ) ;
    Root2 = Find ( S , X2 ) ;
    if ( Root != Root2 ) S[ Root2 ] . Parent = Root1 ;
    }

    这样并集后,树越来越大越来越高,树高了之后Find函数效率会变差,所以尽量把小的集合并到大的集合中去;用负数代表根结点,负数的绝对值为这棵树的元素个数

    相关文章

      网友评论

          本文标题:哈夫曼树与哈夫曼编码、集合

          本文链接:https://www.haomeiwen.com/subject/xwscjttx.html