【算法基础】----贪心算法的应用之Huffman编码

1.Huffman树的基础概念

路径:从树中的一个节点到另一个节点之间的分支构成这两个节点的路径。

路径长度:路径上分支的数目。

树的路径高度:从根到每一个节点的路径之和。

节点的带权路径长度:从该节点到树根之间的路径长度与节点上权的乘积。

树的带权路径长度:树中所有叶节点的带权路径长度之和,通常记作WPL(Weighted Path Length of Tree)。

Huffman树:假设有n个权值{m1,m2,m3,,,,,mn},可以构造一棵具有n个叶子节点的二叉树,每个叶子节点带权为mi,则其中带权路径长度WPL最小的二叉树称作Huffman树。



根据定义,Huffman树是带权路径长度最小的树,假设一个二叉树有4个节点,分别是A,B,C,D,其权重分别是5,7,2,13,通过这4个节点可以构成多种二叉树。


WPL(A)=5*2 + 7*2 + 2*2 + 13*2 = 54

WPL(B)=5*1 + 7*2 + 2*3 + 13*3 = 64

WPL(C)=13*1 + 7*2 + 2*3 + 5*3 = 48

通过上面的例子,其实可以发现的规律是在构造二叉树的时候,将权值大的节点尽量放在路径高度小的位置,而将权值小的节点尽量放在路径高度大的位置,这样就可以得到最优的二叉树。



2.Huffman树的构造过程

(1)将给定的n个权值{m1,m2,m3,,,,,mn}作为n个根节点的权值并构造一棵具有n棵二叉树的森林{T1,T2,T3,,,,Tn},其中每棵二叉树都只有一个节点。

(2)在森林中选取两棵根节点权值最小的二叉树,作为左右子树并构造一棵二叉树,新二叉树的根节点权值为这两棵树根的权值之和。

(3)在森林中,将上面选择的两棵权值最小的二叉树从森林中删除,同时将上一步中新构造的二叉树加入到森林中。

(4)重复(2)(3)步骤,直到森林中只有一棵二叉树为止,这棵就是Huffman树。




3.Huffman编码

在现实中,如果要使用ASCII码来设计电文,会浪费许多的空间,因为所有的编码都是7位。因此为了节省空间,可以将电文设计成二进制前缀编码,其实就是以n种字符的出现频率作为权重,然后设计Huffman树的过程。正是因为这个原因,所以通常将二进制前缀编码称为Huffman编码。


Huffman树的数据结构

(1).我们采用数组的形式来保存Huffman树,所以在该结构父节点,左右子树节点都保存在数组对应的下标位置,因此不采用指针变量,还需要定义一个字符数指针,用来指向Huffman编码字符串。

<pre name="code" class="cpp">typedef struct HuffmanTree {
        int weight ;
        int parent ;
        int left ;
        int right ;
}HuffmanTree ;

typedef char * HuffmanCode ;


 
(2).编写创建Huffman树的代码 

<pre name="code" class="cpp">void CreateTree(HuffmanTree *HuffTree , int n , int * w) {
        int i , m = 2 * n - 1 ;
        int bt1 , bt2 ;
        if(n <= 1)
                return ;
        for(i = 1 ; 1 <= n ; ++ i) {
                HuffTree[i].weight = w[i-1] ;
                HuffTree[i].parent = 0 ;
                HuffTree[i].left = 0 ;
                HuffTree[i].right = 0 ;
        }
        for(;i <= m ; ++ i){
                HuffTree[i].weight = 0 ;
                HuffTree[i].parent = 0 ;
                HuffTree[i].left = 0 ;
                HuffTree[i].right = 0 ;
        }
        for(i = n + 1 ; i < m ; ++ i) {
                SelectNode(HuffTree , i -1 , &bt1 , &bt2) ;
                HuffTree[bt1].parent = i ;
                HuffTree[bt2].parent = i ;
                HuffTree[i].left = bt1 ;
                HuffTree[i].right = bt2 ;
                HuffTree[i].weight = HuffTree[bt1].weight + HuffTree[bt2].weight ;
        }
}


 

其中参数:

HuffTree :指向HuffTree树的指针,调用函数申请内存,并得到这个指针

n :创建HuffTree树的叶节点数量

w :是一个指针,用于传入n个节点的权值

(3).编写SelectNode()函数,功能是在创建Huffman树函数CreateTree()中反复使用,该函数用于从无父节点中选出2个权值最小节点

void SelectNode(HuffmanTree * HuffTree , int n , int * bt1 , int * bt2) {
        //从1-x个节点中选择parent节点为0,权重最小的两个节点
        int i ;
        HuffmanTree *HuffTree1 , *HuffTree2 , *t ;
        HuffTree1 = NULL ;
        HuffTree2 = NULL ;                  //初始化两个节点为空
        for(i = 1 ; i <= n ; ++ i) {                    //循环处理1-n个节点
                if(HuffTree[i].parent = 0) {            //父节点为空

                        //找到第一个父节点为空的节点,作为HuffTree1

                        if(HuffTree1 == NULL) {
                                HuffTree1 = HuffTree + i ;              //指向第i个节点
                                continue ;                              //继续循环
                        }

                        //找到第二个父节点为空的节点,作为HuffTree2

                        if(HuffTree2 == NULL) {                         //节点指针2为空
                                HuffTree2 = HuffTree + i ;              //指向第i个节点
                                if(HuffTree1 ->weight > HuffTree2 ->weight) {   //比较两个节点的权重,使HuffTree1指向的节点权重小
                                        t = HuffTree2 ;
                                        HuffTree2 = HuffTree1 ;
                                        HuffTree1 = t ;
                                }
                                continue ;
                        }
                        if(HuffTree1 && HuffTree2) {                    //若HuffTree1,HuffTree2两个指针都有效
                                if(HuffTree[i].weight <= HuffTree1 ->weight) {  //第i个节点权重小于HuffTree1指向的节点
                                        HuffTree2 = HuffTree1 ;                 //
                                        HuffTree1 = HuffTree + i ;              //
                                }
                                else if(HuffTree[i].weight < HuffTree2 ->weight) {      //
                                        HuffTree2 = HuffTree + i ;              //
                                }
                        }
                }
        }
        if(HuffTree1 > HuffTree2) {             //增加比较,使二叉树左侧为叶子节点
                *bt2 = HuffTree1 - HuffTree ;
                *bt1 = HuffTree2 - HuffTree ;
        }
        else {
                *bt2 = HuffTree2 - HuffTree ;
                *bt1 = HuffTree1 - HuffTree ;
        }

void HuffmanCoding(HuffmanTree *HuffTree , int n , HuffmanCode * HuffCode) {
        char * cd ;
        int start , i ;
        int current , parent ;
        cd = (char *)malloc(n * sizeof(char)) ;         //
        cd[n-1] = '\0' ;                                //
        for(i = 1 ; i <= n ; ++i) {
                start = n - 1 ;
                current = i ;
                parent = HuffTree[current].parent ;
                while(parent) {
                        if(current == HuffTree[parent].left)
                                cd[--start] = '0' ;
                        else
                                cd[--start] = '1' ;
                        current = parent ;
                        parent = HuffTree[parent].parent ;
                }
                HuffCode[i-1] = (char *)malloc((n - start) * sizeof(char)) ;
                strcpy(HuffCode[i-1] , & cd[start]) ;

        }
        free(cd) ;
}



int main(void) {
        int i , n = 4 , m ;
        char test[] = "DBDBDABDCDADBDADBDADACDBDBD" ;
        char code[100] , code1[100] ;
        char alphabte[] = { 'A' , 'B' , 'C' , 'D' } ;
        int w[] = {5 , 7 , 2 , 13} ;
        HuffmanTree *HuffTree ;
        HuffmanCode * HuffCode ;
        m = 2 * n - 1 ;
        HuffTree = (HuffmanTree *)malloc((m + 1) * sizeof(HuffmanTree)) ;
        HuffCode = (HuffmanCode *)malloc((n * sizeof(char *))) ;
        CreateTree(HuffTree , n , HuffCode) ;
        HuffmanCoding(HuffTree , n , HuffCode) ;
        for(i = 1 ; i <= n ; ++ i)
                printf("字母:%c , 权重:%d , 编码为:%s\n" , alphabte[i - 1] , HuffTree[i].weight , HuffCode[i - 1]) ;
        return 0 ;
}
</pre><pre name="code" class="cpp">



此外,还有一种传统的树形结构的方法,优点是较为直观,能够形象的模拟出构造Huffman树的过程,但缺点是较为浪费空间。



#include <stdio.h>
#include <stdlib.h>

typedef struct HuffTree {
        char ch ;
        int weight ;
        struct HuffTree * left ;
        struct HuffTree * right ;
}HuffTree ;

void Huffman(HuffTree ** forest , int num) {
        int min_1 , min_2 ;
        int i , j ;
        HuffTree * newNode = NULL ;
        for(i = 0 ; i < num -1 ; ++ i) {
                //找到第一个没有父节点的二叉树
                for(min_1 = 0 ; min_1 < num && forest[min_1] == NULL ; min_1 ++) ;
                //找到第二个没有父节点的二叉树
                for(min_2 = min_1 ++ ; min_2 < num && forest[min_2] == NULL ; min_2 ++) ;
                //通过一次遍历找到权值最小和次小的两个二叉树
                //如果初始情况下,min_1的权值大于min_2,那么通过第一次比较就可以交换
                for(j = min_2 ; j < num ; ++ j) {
                        //如果当前节点已经有父节点,就跳过
                        if(forest[j]) {
                                //如果当前节点比最小的节点权值更小,就把它赋给最小,把原来的最小赋给次小
                                if(forest[j] ->weight < forest[min_1] -> weight) {
                                        min_2 = min_1 ;
                                        min_1 = j ;
                                }
                                //而如果当前节点比最小大,比次小小,就把它赋给次小
                                else if(forest[j] -> weight < forest[min_2] -> weight) {
                                        min_2 = j ;
                                }
                                newNode = (HuffTree *) malloc (sizeof(HuffTree)) ;
                                newNode ->weight = forest[min_1] -> weight + forest[min_2] -> weight ;
                                newNode ->left = forest[min_1] ;
                                newNode ->right = forest[min_2] ;
                                forest[min_1] = newNode ;
                                forest[min_2] = NULL ;
                        }
                }
        }
}

int main(void) {
        int num ;
        int i ;
        printf("Input the num of codes:") ;
        scanf("%d" , num) ;
        HuffTree ** forest = (HuffTree **) malloc (num * sizeof(HuffTree *)) ;
        //initialization forest
        for(i = 0 ; i < num ; ++ i) {
                forest[i] = (HuffTree *) malloc (sizeof(HuffTree)) ;
                printf("input the No.%d HuffTree's charactor:" , i)  ;
                scanf("%c" , forest[i] -> ch) ;
                printf("input the No.%d HuffTree's charactor:" , i) ;
                scanf("%d" , forest[i] -> weight) ;
                forest[i] -> right = NULL ;
                forest[i] -> left = NULL ;
        }
        Huffman(forest , num) ;
        return 0 ;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值