数据结构与算法之哈夫曼树与哈夫曼编码

哈夫曼树

对于哈夫曼树,凡是学过离散数学的同学肯定不会陌生,这里我们简略它的介绍。
哈夫曼树就是最优二叉树,即叶子个数和各个叶子的权值确定的情况下,树的带权路径长度(树中所有叶子结点的带权路径(带权路径即结点到树根的路径长度与结点权的乘积)之和)WPL最小。

具体画哈夫曼树的方法见:https://jingyan.baidu.com/article/a501d80c16dfa0ec620f5e70.html

对于哈夫曼树的存储我们一般采用静态三叉链表,如下所示:
在这里插入图片描述
weight:权值
Parent:双亲结点在数组中的下标
Lchild/Rchild:左/右孩子在数组中的下标
存储结构我们定义为:

typedef struct{
    int weight;//权值
    int parent;
    int LChild;
    int RChild;
}HTnode,HaffmanTree[M+1];//0号单元不使用

算法实现

哈夫曼算法的实现
step1: 初始化所有结点:构建n个root 将Array的前n个元素视为root,其权值为Wi,孩子和双亲指针全为0,置空后n-1各元素。
step2: 构建哈夫曼树:在数组的已有结点中选双亲为0且weight最小的两节点,构建新节点,结点下标为数组中已有结点的后一个位置,同时其权值选两权值最小的两节点的权值之和
step3: 其左右孩子分别指向两权值最小结点,同时,两权值最小结点的双亲应该为指向新节点,重复n-1次
//n是叶子节点数,w[]存储权值

 void CrtHaffmanTree(HaffmanTree ht, int w[], int n)
{
    int m = 2 * n - 1;
    int i;
    int s1, s2;
    for (i = 1; i <= n;i++){
        ht[i].weight = w[i];
        ht[i].LChild = 0;
        ht[i].parent = 0;
        ht[i].RChild = 0;
    }//构建n个root 将Array的前n个元素视为root,其权值为Wi,孩子和双亲指针全为0,
    for (i = n + 1; i <= m;i++)
    {
        ht[i].weight = 0;
        ht[i].LChild = 0;
        ht[i].parent = 0;
        ht[i].RChild = 0;
    }//置空后n-1各元素

    //构建哈夫曼树
    for (i = n + 1; i <= m;i++)
    {
        /*在ht的前i-1项选双亲为0且权值最小的两节点s1,s2*/
        select(ht, i - 1, &s1, &s2);
        ht[i].weight = ht[s1].weight + ht[s2].weight;
        ht[i].LChild = s1;
        ht[i].RChild = s2;/*更改孩子*/
        ht[s1].parent = i;
        ht[s2].parent = i;/*更改s1,s2的双亲*/
        //printf("%d:%d %d %d\n", i,ht[i].weight,ht[i].LChild,ht[i].RChild);
    }
}

在step2中:”在数组的已有结点中选双亲为0且weight最小的两节点“这句话我们单独用一个select进行实现,具体如下:

void select(HaffmanTree ht, int end, int *s1,int *s2)
{
    int min1, min2;
    //数组的起始位置
    int i = 1;
    //首先找到两个无父结点的结点(说明还未使用其构建成树)
    while(ht[i].parent!=0&&i<=end)
        i++;

    min1 = ht[i].weight;
    *s1 = i;

    i++;
    while(ht[i].parent!=0&&i<=end)
        i++;
    
    if(ht[i].weight<min1)
    {
        min2 = min1;
        *s2 = *s1;
        min1 = ht[i].weight;
        *s1 = i;
    }
    else
    {
        min2 = ht[i].weight;
        *s2 = i;
    }
   //两个结点和后续的所有未构建成树的结点做比较
    for (int j = i + 1; j <= end;j++)
    {
        if(ht[j].parent!=0){
            continue;
        }
        if(ht[j].weight<min1)
        {
            min2 = min1;
            min1 = ht[j].weight;
            *s2 = *s1;
            *s1 = j;
        }
        else if(ht[j].weight>=min1&&ht[j].weight<min2)
        {
            min2 = ht[j].weight;
            *s2 = j;
        }
    }
}

哈夫曼树的算法主要实现方法如上所示。

哈夫曼编码

对于烦赘的概念我们先不说,但是我们需要了解:哈夫曼编码是能够使得信息压缩达到最短的二进制前缀编码,即最优二进制编码。
在这里插入图片描述
如上图所示,我们将左分支记为0,右分支记为1.(哈夫曼编码是前缀码)
D:00
E:01
F:1
/哈夫曼编码是前缀码*
实现哈夫曼编码
**
*1)构建哈夫曼树
*2)在哈夫曼树求各个叶子结点的编码;
(由于哈夫曼编码的长度不一定,所以我们使用一个指针数组<可动态开辟空间>,存放每个编码字的头指针)
typedef char
HaffmanCode[n+1];
**从叶子结点开始,沿双亲链追溯到根节点,追溯过程中每上升一层,则经过了一个分支,便可得到一位哈夫曼编码值(左为0右为1);
**由于从叶子追溯到跟的过程中所得到的码串,恰为哈夫曼编码的逆串,因此在产生哈夫曼编码时,创建一个临时数组,每位编码从后向前放入数组中,由一个指针存放控制的次序
**到达根节点时,一个叶子编码构造完成,此时数组中的指针为开始的串复制到动态申请的编码串空间。
**思路如上

typedef char *HaffmanCode[N + 1];
void CrtHuffmanCode(HaffmanTree ht, HaffmanCode hc,int n)
{
    char *s;//创建的临时数组
    int start,p,c;
    s = (char *)malloc(n * sizeof(char));
    s[n - 1] = '\0';/*有后向前放入编码*/
    for (int i = 1; i <= n;i++)
    {
        start = n - 1;
        c = i;
        p = ht[i].parent;/*c为当前节点,p为其双亲*/
        while(p!=0)
        {
            --start;
            if(ht[i].LChild==c)
                s[start] = '0';
            else
                s[start] = '1';
            c = p;
            p = ht[p].parent;/*上溯一层*/
        }
        hc[i] = (char *)malloc((n - start) * sizeof(char));
        strcpy(hc[i], &s[start]);
    }
    free(s);
}

上面的算法分析十分清楚,可以结合代码来看

完成代码如下:(包含测试主函数)

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//struct
typedef struct{
    int weight;//权
    int parents, Lchild, Rchlid;
}thnode,HuffmanTree[50];
//select
void select(HuffmanTree ht,int end,int*s1,int*s2)
{
    int min1, min2;//小与次小
    int i = 1;
    while(ht[i].parents!=0&&i<=end)
        i++;
    min1 = ht[i].weight;
    *s1 = i;

    i++;
    while(ht[i].parents&&i<=end)
        i++;
    if(ht[i].weight<min1)
    {
        min2 = min1;
        min1 = ht[i].weight;
        *s2 = *s1;
        *s1 = i;
    }
    else
    {
        min2 = ht[i].weight;
        *s2 = i;
    }

    for (int j = i + 1; j <= end;j++)
    {
        if(ht[j].weight<min1)
        {
            min2 = min1;
            min1 = ht[j].weight;
            *s2 = *s1;
            *s1 = j;
        }
        else if(ht[j].weight>=min1&&ht[j].weight<min2){
            min2 = ht[  j].weight;
            *s2 = j;
        }
    }
}

//构建哈夫曼树
void Huffman_Creat(HuffmanTree ht,int w[],int n)//n为叶子结点
{
    //前n置root
    int m = 2 * n - 1;//树的总结点数
    int i,s1,s2;
    for (i = 1; i <= n;i++)
    {
        ht[i].weight = w[i];
        ht[i].Lchild = 0;
        ht[i].Rchlid = 0;
        ht[i].parents = 0;
    }
    for (i = n+1; i <= m;i++)
    {
        ht[i].weight = 0;
        ht[i].Lchild = 0;
        ht[i].Rchlid = 0;
        ht[i].parents = 0;
    }

    for (i = n + 1; i <= m;i++)
    {
        select(ht, i - 1, &s1, &s2);
        ht[i].weight = ht[s1].weight + ht[s2].weight;
        ht[i].Lchild = s1;
        ht[i].Rchlid = s2;
        ht[s1].parents = i;
        ht[s2].parents = i;
    }
}
/**
 * 哈夫曼编码
*/
typedef char* HuffmanCode[20];

void HuffmanCode_Creat(HuffmanTree ht, HuffmanCode hc,int n)
{
    char *s;
    int start;
    int i, c, p;
    s = (char *)malloc(n * sizeof(char));//创建临时数组
    s[n - 1] = '\0';
    for (i = 1; i <= n;i++)
    {
        start = n - 1;
        c = i;//存储当前节点
        p = ht[i].parents;//存双亲
        while(p!=0)
        {
            --start;
            if(ht[p].Lchild==c)
                s[start] = '0';
            else
                s[start] = '1';
            c = p;
            p = ht[p].parents;//上溯
        }
        hc[i] = (char *)malloc((n - start) * sizeof(char));
        strcpy(hc[i], &s[start]);
    }
    free(s);
}
//test.w[i]={1,3,6,9};
int main()
{
    int w[5] = {0, 1, 3, 6, 9};
    HuffmanTree ht;
    Huffman_Creat(ht, w, 4);
    int i, n = 4;
    for (i = 1; i < 2 * n; i++)
    {
        printf("%d: present:%d Lchild:%d Rchild:%d weight:%d\n", i, ht[i].parents, ht[i].Lchild, ht[i].Rchlid, ht[i].weight);
    }
      HuffmanCode hc;
      HuffmanCode_Creat(ht,hc, n);
      for (i = 1; i <=n; i++)
      {
          printf("%d: present:%d Lchild:%d Rchild:%d weight:%d code:%s\n", i, ht[i].parents, ht[i].Lchild, ht[i].Rchlid, ht[i].weight,hc[i]);
      }
    return 0;
}
  • 8
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值