数据结构与算法 - 哈夫曼树

哈夫曼(David Huffman)

著名的 哈夫曼编码 发明人 戴维·霍夫曼于1999年10月17日因癌症去世,享年74岁,他发明了著名的的 霍夫曼编码,除了霍夫曼编码以外,他还涉及出二叉最优搜索树的算法,因为其效率最高,所以被命名为霍夫曼算法,是动态规划的一个范例

这个牛逼的男人活到了74岁,程序员还是蛮高寿的嘛,而且现在医疗手段更发达了,啊哈哈哈不过这头还是秃了,没办法,谁让人聪明呢,匿了匿了 ~

PS:貌似人家不是程序员,人家是科学家好吧….

哈夫曼编码背景

我们来看一个简单的问题,从小到大我们面临了很多的考试,小学-初中-高中… 然后老师对学生是如何进行区分的呢,不能说你考85他考73而让老师记住每个人的分数,而是通过一种分段的方法划分不同的学生:

1
2
3
4
5
6
7
8
9
10
11
if (sum < 60) {
result = "不及格";
} else if (sum < 70) {
result = "及格";
} else if (sum < 80) {
result = "中等";
} else if (sum < 90) {
result = "良好";
} else {
result = "优秀";
}

如图,通过划分及格片段,就可以把数以万计的学生分为5类,老师只用知道这个学生是哪个类别就大概知道他的分数,如此就很方便管理。

我们用二叉树来展示这个判断过程如下:

我们永远是第一个判断这个学生是不是不及格,然后再去判断及格等等,那么问题来了,一个学校的学生中如果大部分都是不及格的,这个方法就很高效,但是很显然,我们大部分人应该集中在中等这个范围,所以基于此我们这个算法就有了优化的地方。

哈夫曼思考

我们需要知道每个分数段都有多少人,大概是这样:

我们会发现 70-79之间的人数最多,那么哈夫曼是如何思考的呢?

那么当然是最先遍历 70 的条件,很大可能第一次就找到结果而不用多次判断,所以这个优化后的二叉树应该是从根到叶子是权值逐渐变小的,所以他的思路就是从叶子开始叶子都是集中找目前最小的两个权值作为左右子树合并为一个父节点以此类推,直到找到根节点即可。

哈弗曼树的构建

1
2
3
4
5
A 5 
E 10
B 15
D 30
C 40

已知我们有5种情况,每种情况对应的权值如上,我们如何构造出一个哈夫曼树。

  1. 首先找到目前权值最小的两个子几点,构成一个二叉树,如下:
  1. 由于A和E生成一个二叉树,其根节点的权值就会变成 A+E = 15 ,所以接下来 我们就要从 N1,B,D,C 中找到最小的两个,PS:其原理就是每次都要找最小的两个权值生成树
  1. 以此类推,最终这个哈弗曼树应该是这样:

代码

const int MaxValue = 10000;//初始设定的权值最大值
const int MaxBit = 4;//初始设定的最大编码位数
const int MaxN = 10;//初始设定的最大结点个数

typedef struct HaffNode{
    int weight;
    int flag;
    int parent;
    int leftChild;
    int rightChild;
}HaffNode;

typedef struct Code//存放哈夫曼编码的数据元素结构
{

    int bit[MaxBit];//数组
    int start;  //编码的起始下标
    int weight;//字符的权值
}Code;

//1.
//根据权重值,构建哈夫曼树;
//{2,4,5,7}
//n = 4;
void Haffman(int weight[],int n,HaffNode *haffTree){

    int j,m1,m2,x1,x2;

    //1.哈夫曼树初始化
    //n个叶子结点. 2n-1
    for(int i = 0; i < 2*n-1;i++){

        if(i<n)
            haffTree[i].weight = weight[i];
        else
            haffTree[i].weight = 0;

        haffTree[i].parent = 0;
        haffTree[i].flag = 0;
        haffTree[i].leftChild = -1;
        haffTree[i].rightChild = -1;
    }


    //2.构造哈夫曼树haffTree的n-1个非叶结点
    for (int i = 0; i< n - 1; i++){
         m1 = m2 = MaxValue;
         x1 = x2 = 0;
        //2,4,5,7
        for (j = 0; j< n + i; j++)//循环找出所有权重中,最小的二个值--morgan
        {
            if (haffTree[j].weight < m1 && haffTree[j].flag == 0)
            {
                m2 = m1;
                x2 = x1;
                m1 = haffTree[j].weight;
                x1 = j;
            } else if(haffTree[j].weight<m2 && haffTree[j].flag == 0)
            {
                m2 = haffTree[j].weight;
                x2 = j;
            }
        }

        //3.将找出的两棵权值最小的子树合并为一棵子树
        haffTree[x1].parent = n + i;
        haffTree[x2].parent = n + i;
        //将2个结点的flag 标记为1,表示已经加入到哈夫曼树中
        haffTree[x1].flag = 1;
        haffTree[x2].flag = 1;
        //修改n+i结点的权值
        haffTree[n + i].weight = haffTree[x1].weight + haffTree[x2].weight;
        //修改n+i的左右孩子的值
        haffTree[n + i].leftChild = x1;
        haffTree[n + i].rightChild = x2;
    }

}
/*
 9.2 哈夫曼编码
 由n个结点的哈夫曼树haffTree构造哈夫曼编码haffCode
 //{2,4,5,7}
 */

void HaffmanCode(HaffNode haffTree[], int n, Code haffCode[])
{
    //1.创建一个结点cd
    Code *cd = (Code * )malloc(sizeof(Code));
    int child, parent;
    //2.求n个叶结点的哈夫曼编码
    for (int i = 0; i<n; i++)
    {
        //从0开始计数
        cd->start = 0;
        //取得编码对应权值的字符
        cd->weight = haffTree[i].weight;
        //当叶子结点i 为孩子结点.
        child = i;
        //找到child 的双亲结点;
        parent = haffTree[child].parent;
        //由叶结点向上直到根结点
        while (parent != 0)
        {
            if (haffTree[parent].leftChild == child)
                cd->bit[cd->start] = 0;//左孩子结点编码0
            else
                cd->bit[cd->start] = 1;//右孩子结点编码1
            //编码自增
            cd->start++;
            //当前双亲结点成为孩子结点
            child = parent;
            //找到双亲结点
            parent = haffTree[child].parent;
        }

         int temp = 0;

        for (int j = cd->start - 1; j >= 0; j--){
            temp = cd->start-j-1;
            haffCode[i].bit[temp] = cd->bit[j];
        }

        //把cd中的数据赋值到haffCode[i]中.
        //保存好haffCode 的起始位以及权值;
        haffCode[i].start = cd->start;
        //保存编码对应的权值
        haffCode[i].weight = cd->weight;
    }
}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, 哈夫曼编码!\n");
    int i, j, n = 4, m = 0;

    //权值
    int weight[] = {2,4,5,7};

    //初始化哈夫曼树, 哈夫曼编码
    HaffNode *myHaffTree = malloc(sizeof(HaffNode)*2*n-1);
    Code *myHaffCode = malloc(sizeof(Code)*n);

    //当前n > MaxN,表示超界. 无法处理.
    if (n>MaxN)
    {
        printf("定义的n越界,修改MaxN!");
        exit(0);
    }

    //1. 构建哈夫曼树
    Haffman(weight, n, myHaffTree);
    //2.根据哈夫曼树得到哈夫曼编码
    HaffmanCode(myHaffTree, n, myHaffCode);
    //3.
    for (i = 0; i<n; i++)
    {
        printf("Weight = %d\n",myHaffCode[i].weight);
        for (j = 0; j<myHaffCode[i].start; j++)
            printf("%d",myHaffCode[i].bit[j]);
        m = m + myHaffCode[i].weight*myHaffCode[i].start;
         printf("\n");
    }
    printf("Huffman's WPS is:%d\n",m);

    return 0;
}