【树】哈夫曼树和哈夫曼编码

哈夫曼(Huffman)树,又称最优树,是一类带权路径长度最短的树。

最优二叉树(哈夫曼树)

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

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

树的路径长度:树根到每一结点的路径长度之和。

在结点数目相同的二叉树中,完全二叉树的路径长度最短。

考虑带权的结点

树的带权路径长度WPL为树中所有叶子结点的带权路径长度之和:

带权路径长度最小的二叉树称为最优二叉树。

如下图:计算出第三棵树的WPL最小,所以它为哈夫曼树


建立哈夫曼树的核心是贪心算法:选权值小的树

步骤:

1.根据给定的n个权值构成n棵二叉树,并加入集合F,这n个二叉树只有一个根结点,没有左右子树;

2.选择集合F中根结点权值最小的两棵树作为左右子树构造一棵新二叉树,且新二叉树的根结点的权值为其左右子树上根结点的权值之和;

3.在F中删除这两棵树,加入新得到的二叉树;

4.重复2,3直到F只含一棵树,这棵树便是哈夫曼树;

如下图:(小左大右)

权值越小离根越远,权值越大离根越近

因为要选择根值最小的两个树,所以这个集合中存根的地方用优先级队列,方便出队选择

树的结点结构:

typedef unsigned int WeigthType;
typedef unsigned int NodeType;
typedef struct//结点结构
{
    WeigthType weigth;
    NodeType parent, leftchild, rightchild;
}HTNode;
typedef HTNode HuffmanTree[m];

初始化

void InitHuffManTree(HuffmanTree hft,WeigthType w[])
{
    memset(hft, 0, sizeof(HuffmanTree));
    for (int i = 0; i < n; i++)
    {
        hft[i + 1].weigth = w[i];//0下标不用,所以+1
    }
//printf("HuffManTree:size%d\n", sizeof(HuffmanTree));//16*16=256(数组类型m(6)个*结构体大小(16))
//printf("hft:        size%d\n", sizeof(hft));//4,数组类型在这当指针
//printf("w:          size%d\n", sizeof(w));//4,指针
}

构建哈夫曼树

因为用优先级队列,我们需要知道结点的原始下标,所以还需创建结构体(包含权值和下标 )传入队列

struct IndexWeigth
{
    int index;//下标
    WeigthType weight;//权值
    operator WeigthType()const { return weight; }
};

创建

方法为上面的步骤

void CreatHuffManTree(HuffmanTree hft)
{
    priority_queue<IndexWeigth, vector<IndexWeigth>,greater<IndexWeigth>>qu;//greater比较权值
    for (int i = 1; i <= n; i++)
    {
        qu.push(IndexWeigth{ i,hft[i].weigth });//把叶子结点放到优先级队列内
    }
    int k = n + 1;//分支结点的下标
    while (!qu.empty())//取树构建新树
    {
        if (qu.empty())break;
        IndexWeigth left = qu.top(); qu.pop();//取第一小为左边
        if (qu.empty())break;
        IndexWeigth right = qu.top(); qu.pop();//取第二小为右边
//在分支结点上构建新树
        hft[k].weigth = left.weight + right.weight;
        hft[k].leftchild = left.index;
        hft[k].rightchild = right.index;
        hft[left.index].parent = k;
        hft[right.index].parent = k;
//将新树放入优先级队列
        qu.push(IndexWeigth{ k,hft[k].weigth });
        k++;
    }
}

输出

因为要建树,输出需要知道每个结点的权值、双亲和左右孩子

void PrintHuffManTree(HuffmanTree hft)
{
    for (int i = 1; i < m; i++)
    {
        printf("index%3d weight:%-3d parent:%-3d Lchild:%-3d Rchild:%-3d\n ",
              i,hft[i].weigth,hft[i].parent,hft[i].leftchild,hft[i].rightchild);
    }
    printf("\n");
}

主函数

int main()
{
    WeigthType w[n] = { 5,29,7,14,8,23,3,11 };
    HuffmanTree hft = { 0 };
    printf("初始带权树集合:\n");
    InitHuffManTree(hft, w);//初始化
    PrintHuffManTree(hft);//输出
    printf("哈夫曼树的结点关系:\n");
    CreatHuffManTree(hft);//创建
    PrintHuffManTree(hft);
    return 0;
}

最终结果画出的图:

A:11111 B :10 C :1110 D:110 E :000 F :01 G:11110 H:001


哈夫曼编码

发送电文时希望总长度尽可能的短(压缩),但是必须任意一个字符的编码不能是另一个字符的前缀,否则会有歧义。这种不是其他字符前缀编码的编码叫前缀码。

用哈夫曼树可以创建编码:其左分支表示字符‘0’,右分支表示字符‘1’,可以从根结点到叶子结点的路径上分支字符组成的字符串作为该叶子结点字符的编码。

叶子只有一个双亲所以创建编码时从叶子节点开始,最后把编码倒回来

编码结构体:

字符和其编码

typedef struct
{
    char ch;
    char code[n + 1];
}HuffCodeNode;
typedef HuffCodeNode HuffCoding[n + 1];//0下标不放数据

编码初始化

void InitHuffManCode(HuffCoding hc, const char* ch)
{
    memset(hc, 0, sizeof(HuffCoding));
    for (int i = 1; i <= n; i++)
    {
        hc[i].ch = ch[i - 1];
        hc[i].code[0] = '\0';
    }
}

创建编码

void CreatHuffManCode(HuffmanTree hft, HuffCoding hc)
{
    char code[n + 1] = { 0 };
    for (int i = 1; i <= n; i++)
    {//倒过来放
        int k = n;
        code[k] = '\0';
        int c = i;//孩子
        int pa = hft[c].parent;//找双亲
        while (pa != 0)//没有到根结点
        {
            code[--k] = hft[pa].leftchild==c ? '0' : '1';//是左孩子0,是右孩子1
            c = pa;
            pa = hft[c].parent;
        }
        //正着拷贝
        strcpy_s(hc[i].code, n, &code[k]);
    }
}

输出编码

void PrintfHuffManCode(HuffCoding hc)
{
    for (int i = 1; i <= n; i++)
    {
        printf("data:%c ->code:%s\n ", hc[i].ch, hc[i].code);
    }
}

主函数

int main()
{
    WeigthType w[n] = { 5,29,7,14,8,23,3,11 };
    char ch[n + 1] = "ABCDEFGH";
    HuffmanTree hft = { 0 };
    HuffCoding hc = { 0 };
    //初始带权树集合
    InitHuffManTree(hft, w);
    //编码初始化
    InitHuffManCode(hc, ch);
    //创建哈夫曼树
    CreatHuffManTree(hft);
    //创建编码
    CreatHuffManCode(hft, hc);
    //输出树结点关系
    PrintHuffManTree(hft);
    //输出编码
    PrintfHuffManCode(hc);
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

曦樂~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值