赫夫曼树和赫夫曼编码详解

news2025/1/31 11:11:29

目录

何为赫夫曼树?

赫夫曼树算法

赫夫曼编码

编程实现赫夫曼树

编程实现赫夫曼编码

 编程实现WPL

总代码及分析


何为赫夫曼树?

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

结点的带权路径长度:从树根到该结点的路径长度与结点上权的乘积

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

 假设有n个权值(w1.w2,,,Wn),试构造一棵有n个叶子结点的二叉树,每个叶子结点带权为Wi,则其中WPL最小的二叉称做最优二叉树或赫夫曼树。

赫夫曼树算法

(1)根据给定的n个权值(w1,w2,…,Wn,)构成n棵二叉树的集合F=(T1,T2,…,Tn,其中每棵二叉树Ti中只有一个带权为wi的根结点,其左右子树均空。

(2)在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右子树上根结点的权值之和。

(3)在F中删除这两棵树,同时将新得到的二叉树加入F中。

(4)重复(2)和(3),直到F只含一棵树为止。这棵树便是赫夫曼树。

如下图:

赫夫曼编码

设一课二叉树为:

其3个叶子结点分别表示a、b、c3个字符, 约定左分支表示字符‘0’,右分支表示字符‘1’。则可以从根结点到叶子结点的路径上分支字符组成的字符串作为该叶子结点字符的编码。如上图可以得到a、b、c的二级制前缀编码分别为:0、10、11。

如何得到使电文总长最短的二级制编码?假设每种字符在电文中出现的次数为i,编码长度为j,而电文只有n种字符,则电文总长为n种i*j的和。对应到二叉树上,若i表示叶子结点的权值,j为从根到叶子的路径长度。则电文总长恰为二叉树带权路径长度。所以,设计电文总长最短的二级制前缀编码即以N种字符出现的频率作为权值,设计一颗赫夫曼树的问题

编程实现赫夫曼树

//哈夫曼树的存储表示
typedef struct
{
    int weight;                 //节点的权值
    int parent, lchild, rchild; //节点的双亲,左孩子和右孩子
} HTNode, * HuffmanTree;



typedef char** HuffmanCode;



//查权值最小且双亲为0的节点
void Select(HuffmanTree HT, int len, int& s1, int& s2)
{
    int i, min1 = 0x3f3f3f3f, min2 = 0x3f3f3f3f; //先赋予最大值
    for (i = 1; i <= len; i++)
    {
        if (HT[i].weight < min1 && HT[i].parent == 0)
        {
            min1 = HT[i].weight;
            s1 = i;
        }
    }
    int temp = HT[s1].weight; //将原值存放起来,然后先赋予最大值,防止s1被重复选择
    HT[s1].weight = 0x3f3f3f3f;
    for (i = 1; i <= len; i++)
    {
        if (HT[i].weight < min2 && HT[i].parent == 0)
        {
            min2 = HT[i].weight;
            s2 = i;
        }
    }
    HT[s1].weight = temp; //恢复原来的值
}



void CreatHuffmanTree(HuffmanTree& HT, int n,int*w)
{
    //构造赫夫曼树HT
    int m, s1, s2, i;
    if (n <= 1)
        return;
    m = 2 * n - 1;
    HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode)); //0号单元未用,所以需要动态分配m+1个单元,HT[m]表示根结点
    for (i = 1; i <= m; ++i) //将1~m号单元中的权重、双亲、左孩子,右孩子的下标都初始化为0
    {
        HT[i].parent = 0;
        HT[i].lchild = 0;
        HT[i].rchild = 0;
        HT[i].weight = 0;
    }
    for (i = 1; i <= n; ++i) //输入前n个单元中叶子结点的权值
    {
        HT[i].weight = *w;
        w++;
    }
    /*――――――――――初始化工作结束,下面开始创建赫夫曼树――――――――――*/
    for (i = n + 1; i <= m; ++i)
    { //通过n-1次的选择、删除、合并来创建赫夫曼树
        Select(HT, i - 1, s1, s2);
        //在HT[k](1≤k≤i-1)中选择两个其双亲域为0且权值最小的结点,
        // 并返回它们在HT中的序号s1和s2
        HT[s1].parent = i;
        HT[s2].parent = i;//这里双亲不为0,相当于把s1和s2删除
        //得到新结点i,从森林中删除s1,s2,将s1和s2的双亲域由0改为i
        HT[i].lchild = s1;
        HT[i].rchild = s2;                            //s1,s2分别作为i的左右孩子
        HT[i].weight = HT[s1].weight + HT[s2].weight; //i 的权值为左右孩子权值之和
    }                                                 
}

编程实现赫夫曼编码

/*
从叶子节点到根节点逆向求赫夫曼树HT中n个叶子节点的赫夫曼编码,并保存在HC中
*/
void HuffmanCoding(HuffmanTree&HT, HuffmanCode& HC, int n)
{
    //用来保存指向每个赫夫曼编码串的指针
    HC = (HuffmanCode)malloc((n+1) * sizeof(char*));
    if (!HC)
    {
        exit(-1);
    }
    //临时空间,用来保存每次求得的赫夫曼编码串
    //对于有n个叶子节点的赫夫曼树,各叶子节点的编码长度最长不超过n-1
    //外加一个'\0'结束符,因此分配的数组长度最长为n即可
    char* code = (char*)malloc(n * sizeof(char));
    if (!code)
    {
        printf("code malloc faild!");
        exit(-1);
    }

    code[n - 1] = '\0';  //编码结束符,亦是字符数组的结束标志
                         //求每个字符的赫夫曼编码
    int i;
    for (i = 1; i <= n; i++)
    {
        int current = i;           //定义当前访问的节点
        int father = HT[i].parent; //当前节点的父节点
        int start = n - 1;           //每次编码的位置,初始为编码结束符的位置
                                     //从叶子节点遍历赫夫曼树直到根节点
        while (father !=0)
        {
            if (HT[father].lchild == current)   //如果是左孩子,则编码为0
                code[--start] = '0';
            else                              //如果是右孩子,则编码为1       
                code[--start] = '1';
            current = father;
            father = HT[father].parent;
        }

        //为第i个字符的编码串分配存储空间
        HC[i] = (char*)malloc((n - start) * sizeof(char));
        if (!HC[i])
        {
            printf("HC[i] malloc faild!");
            exit(-1);
        }
        //将编码串从code复制到HC
        strcpy(HC[i], code + start);
    }
    for (int i = 1; i <=n; ++i) {
    printf("%s\n", HC[i]);
    }
    free(code); //释放保存编码串的临时空间
}

 编程实现WPL

从叶子结点开始遍历二叉树直到根结点,根结点为HT[2n-1],且HT[2n-1].parent=0;
各叶子结点为HT[1]、HT[2]...HT[n]。
关键步骤是求出各个叶子结点的路径长度,用此路径长度*此结点的权值就是
此结点带权路径长度,最后将各个叶子结点的带权路径长度加起来即可。

int countWPL1(HuffmanTree HT, int n)
{
    int i, countRoads, WPL = 0;
    /*
    由creat_huffmanTree()函数可知,HT[1]、HT[2]...HT[n]存放的就是各个叶子结点,
    所以挨个求叶子结点的带权路径长度即可
    */
    for (i = 1; i <=n; i++)
    {
        int father = HT[i].parent; //当前节点的父节点
        countRoads = 0;//置当前路径长度为0
        //从叶子节点遍历赫夫曼树直到根节点
        while (father != 0)
        {
            countRoads++;
            father = HT[father].parent;
        }
        WPL += countRoads * HT[i].weight;
    }
    return WPL;
}

总代码及分析

#include <stdio.h>
#include<stdlib.h>
#include<string>
//哈夫曼树的存储表示
typedef struct
{
    int weight;                 //节点的权值
    int parent, lchild, rchild; //节点的双亲,左孩子和右孩子
} HTNode, * HuffmanTree;
typedef char** HuffmanCode;
//查权值最小且双亲为0的节点
void Select(HuffmanTree HT, int len, int& s1, int& s2)
{
    int i, min1 = 0x3f3f3f3f, min2 = 0x3f3f3f3f; //先赋予最大值
    for (i = 1; i <= len; i++)
    {
        if (HT[i].weight < min1 && HT[i].parent == 0)
        {
            min1 = HT[i].weight;
            s1 = i;
        }
    }
    int temp = HT[s1].weight; //将原值存放起来,然后先赋予最大值,防止s1被重复选择
    HT[s1].weight = 0x3f3f3f3f;
    for (i = 1; i <= len; i++)
    {
        if (HT[i].weight < min2 && HT[i].parent == 0)
        {
            min2 = HT[i].weight;
            s2 = i;
        }
    }
    HT[s1].weight = temp; //恢复原来的值
}

void CreatHuffmanTree(HuffmanTree& HT, int n,int*w)
{
    //构造赫夫曼树HT
    int m, s1, s2, i;
    if (n <= 1)
        return;
    m = 2 * n - 1;
    HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode)); //0号单元未用,所以需要动态分配m+1个单元,HT[m]表示根结点
    for (i = 1; i <= m; ++i) //将1~m号单元中的权重、双亲、左孩子,右孩子的下标都初始化为0
    {
        HT[i].parent = 0;
        HT[i].lchild = 0;
        HT[i].rchild = 0;
        HT[i].weight = 0;
    }
    for (i = 1; i <= n; ++i) //输入前n个单元中叶子结点的权值
    {
        HT[i].weight = *w;
        w++;
    }
    /*――――――――――初始化工作结束,下面开始创建赫夫曼树――――――――――*/
    for (i = n + 1; i <= m; ++i)
    { //通过n-1次的选择、删除、合并来创建赫夫曼树
        Select(HT, i - 1, s1, s2);
        //在HT[k](1≤k≤i-1)中选择两个其双亲域为0且权值最小的结点,
        // 并返回它们在HT中的序号s1和s2
        HT[s1].parent = i;
        HT[s2].parent = i;//这里双亲不为0,相当于把s1和s2删除
        //得到新结点i,从森林中删除s1,s2,将s1和s2的双亲域由0改为i
        HT[i].lchild = s1;
        HT[i].rchild = s2;                            //s1,s2分别作为i的左右孩子
        HT[i].weight = HT[s1].weight + HT[s2].weight; //i 的权值为左右孩子权值之和
    }                                                 
} // CreatHuffmanTree
/*
从叶子节点到根节点逆向求赫夫曼树HT中n个叶子节点的赫夫曼编码,并保存在HC中
*/
void HuffmanCoding(HuffmanTree&HT, HuffmanCode& HC, int n)
{
    //用来保存指向每个赫夫曼编码串的指针
    HC = (HuffmanCode)malloc((n+1) * sizeof(char*));
    if (!HC)
    {
        exit(-1);
    }
    //临时空间,用来保存每次求得的赫夫曼编码串
    //对于有n个叶子节点的赫夫曼树,各叶子节点的编码长度最长不超过n-1
    //外加一个'\0'结束符,因此分配的数组长度最长为n即可
    char* code = (char*)malloc(n * sizeof(char));
    if (!code)
    {
        printf("code malloc faild!");
        exit(-1);
    }

    code[n - 1] = '\0';  //编码结束符,亦是字符数组的结束标志
                         //求每个字符的赫夫曼编码
    int i;
    for (i = 1; i <= n; i++)
    {
        int current = i;           //定义当前访问的节点
        int father = HT[i].parent; //当前节点的父节点
        int start = n - 1;           //每次编码的位置,初始为编码结束符的位置
                                     //从叶子节点遍历赫夫曼树直到根节点
        while (father !=0)
        {
            if (HT[father].lchild == current)   //如果是左孩子,则编码为0
                code[--start] = '0';
            else                              //如果是右孩子,则编码为1       
                code[--start] = '1';
            current = father;
            father = HT[father].parent;
        }

        //为第i个字符的编码串分配存储空间
        HC[i] = (char*)malloc((n - start) * sizeof(char));
        if (!HC[i])
        {
            printf("HC[i] malloc faild!");
            exit(-1);
        }
        //将编码串从code复制到HC
        strcpy(HC[i], code + start);
    }
    for (int i = 1; i <=n; ++i) {
    printf("%s\n", HC[i]);
    }
    free(code); //释放保存编码串的临时空间
}
/*
从叶子结点开始遍历二叉树直到根结点,根结点为HT[2n-1],且HT[2n-1].parent=0;
各叶子结点为HT[1]、HT[2]...HT[n]。
关键步骤是求出各个叶子结点的路径长度,用此路径长度*此结点的权值就是
此结点带权路径长度,最后将各个叶子结点的带权路径长度加起来即可。
*/
int countWPL1(HuffmanTree HT, int n)
{
    int i, countRoads, WPL = 0;
    /*
    由creat_huffmanTree()函数可知,HT[1]、HT[2]...HT[n]存放的就是各个叶子结点,
    所以挨个求叶子结点的带权路径长度即可
    */
    for (i = 1; i <=n; i++)
    {
        int father = HT[i].parent; //当前节点的父节点
        countRoads = 0;//置当前路径长度为0
        //从叶子节点遍历赫夫曼树直到根节点
        while (father != 0)
        {
            countRoads++;
            father = HT[father].parent;
        }
        WPL += countRoads * HT[i].weight;
    }
    return WPL;
}

int main()
{
    HuffmanTree HT;
    HuffmanCode HC;
    int n;
    printf("请输入叶子结点的个数:\n");
    scanf("%d", &n);
    int w[8] = { 2, 15, 30, 8, 10, 5, 12, 18 };
    CreatHuffmanTree(HT, n,w);
    printf("哈夫曼树建立完毕!\n");
    printf("各字符赫夫曼编码为:\n");
    HuffmanCoding(HT, HC, n);
    printf("带权路径最小为:\n");
    printf("%d", countWPL1(HT, n));

}

测试:

 测试中的数据结构:

HT:

初始化后

 

创建后:

 

HC

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/521193.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023网络安全十大顶级工具

从事网络安全工作&#xff0c;手上自然离不开一些重要的网络安全工具。今天&#xff0c;分享10大网络安全工具。 一、Kali Linux Kali 是一个基于 Debian 的 Linux 发行版。它的目标就是为了简单&#xff1a;在一个实用的工具包里尽可能多的包含渗透和审计工具。Kali 实现了这…

【AI面试】CNN 和 transformer 的问题汇总

​ CNN卷积神经网络和transformer相关的知识&#xff0c;是AI的一个基础的&#xff0c;也是前言的知识点。一般面试官会从以下这些问题入手&#xff1a; 卷积神经网络&#xff0c;有什么特点&#xff1f;1*1卷积核有什么作用&#xff1f;计算经过卷积的输出尺寸大小空洞卷积你…

机器学习之朴素贝叶斯三、拉普拉斯平滑技术、优化改进情感分析

文章目录 一、前文问题1. 先看下改进前我们的代码计算部分2. 问题分析&#xff1a; 二、针对问题进行解决1. 什么是拉普拉斯平滑技术2. 拉普拉斯优化-下溢上溢问题3. 改进地方分析&#xff1a;4.改进优化1.优化一&#xff0c;对条件概率计算进行优化2.优化二&#xff0c;对后延…

从小白到黑客高手:一份全面详细的学习路线指南

前言 黑客从入门到精通需要经过深入的学习和实践&#xff0c;这是一个需要长时间投入和大量精力的过程。在这份学习路线中&#xff0c;我将为你介绍黑客学习的基本知识和技能&#xff0c;帮助你逐步掌握黑客技能。 黑客 一、入门阶段 1.了解计算机基础知识 学习计算机基础知…

红黑树(小白教学)

分享一个动画展示的网址&#xff1a;Red/Black Tree Visualization (lerogo.com) 将红黑树之前&#xff0c;我们先来了解一下什么叫做2-3树&#xff01;&#xff01;&#xff01; 在我们以前学习的过程中&#xff08;二分搜索树、完全二叉树等&#xff09;结点都是存放了一个元…

Auto_GPT:如何使用Auto-GPT、安装、开发

文章目录 前言一、使用前提二、如何在您的 PC 上安装 Auto-GPT第 1 步&#xff1a;安装 Python第 2 步&#xff1a;获取 Auto-GPT 源代码和 OpenAI API 密钥第 3 步&#xff1a;在 Windows、MacOS和 Linux上安装 Auto-GPT 三、如何在你的 PC 上运行Auto-GPT总结 前言 Auto-GPT…

Redis消息队列(1)

一)消息队列:从字面意思上来看是存储消息的队列&#xff0c;最简单的消息队列包含三个元素: 1)消息队列:存储和管理消息&#xff0c;也被称之为是消息代理 2)生产者:发送消息到消息队列 3)消费者:从消息队列中获取消息并处理消息 4)当有用户想要进行秒杀下单的时候&#xff0c;…

CENTO OS上的网络安全工具(二十一)Hadoop HA swarm容器化集群部署

如果使用swarm来构建Hadoop、Spark之类的集群&#xff0c;一个绕不过去的问题每个容器都需要支持SSH免密互联——因为Hadoop需要。这就需要事先准备可以一键进行集群化部署的SSH镜像。 一、SSH集群及镜像的构建 1. 准备更换镜像源的Centos7 由于Centos7已经停止维护&#xff0c…

stm32 iic驱动ds1307实际使用驱动

本文使用的工程代码如下&#xff1a; (1条消息) stm32iic驱动ds1307实际使用驱动&#xff0c;参考博客&#xff1a;资源-CSDN文库 上次我记得写了一个使用开发板测试DS1307的测试例程&#xff0c;DS1307也是使用测试板&#xff0c;后面项目上具体使用了&#xff0c;又优化了驱…

HelloSpring

1.beans.xml配置文件 在resource资源文件夹下创建beans.xml文件 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance&qu…

我们的愿景是在个人计算机上实现量子霸权

奥维尔号量子计算机 简介 采用扩展的量子二进制算法。在经典计算机上实现量子计算机。我们的景愿是在个人计算机上实现量子霸权。 此计算机的字长是64位&#xff0c;等效数据位为32位字长的量子计算机。我们采用量子扩展二进制&#xff0c;共有&#xff14;个字符:0,1,Q,P可以…

Redis---主从复制 哨兵

目录 一、主从复制 1、什么是主从复制呢&#xff1f; 2、案例演示 2.1 配置文件 2.2 一主二仆 2.2.1 相关题目&#xff1a; 2.3 薪火相传 & 反客为主 3、复制原理和工作流程 3.1、slave启动&#xff0c;同步清初 3.2 首次连接&#xff0c;全量复制 3.…

【CNN】卷积神经网络(LeNet)是什么?如何实现LeNet?

系列文章目录 第一章 深度学习 CNN中的卷积神经网络&#xff08;LeNet&#xff09; 目录 系列文章目录 文章目录 前言 一、卷积神经网络&#xff08;LeNet&#xff09;是什么&#xff1f; 二、LeNet的网络结构 三、实现LeNet模型 ​​​​ 总结 前言 本文主要是介绍卷…

【改进粒子群优化算法】自适应惯性权重粒子群算法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【软考|软件设计师】某计算机系统的CPU主频为2.8GHz

目录 题&#xff1a; CPI MIPS 题&#xff1a; 某计算机系统的CPU主频为2.8GHz。某应用程序包括3类指令&#xff0c;各类指令的CPI &#xff08;执行每条指令所需要的时钟周期&#xff09;及指令比例如下表所示。执行该应用程序时 的平均CPI为______&#xff1b; 运算速度…

面试了一个00后,绝对能称为是内卷届的天花板

前言 公司前段缺人&#xff0c;也面了不少测试&#xff0c;结果竟然没有一个合适的。一开始瞄准的就是中级的水准&#xff0c;也没指望来大牛&#xff0c;提供的薪资也不低&#xff0c;面试的人很多&#xff0c;但平均水平很让人失望。令我印象最深的是一个00后测试员&#xf…

DelphiMVCFrameWork 源码分析(三)

中间件(MiddleWare) 文档中是这样说的&#xff1a; Middleware is a powerful and flexible layer within DMVCFramework. Middleware allows you to write SOLID code and separate the processing or each request into smaller steps to be invoked during the request ha…

二本4年测试经验,5面阿里(定薪38K),分享我的心得

年前准备跳槽&#xff0c;先后面试了各大小公司&#xff0c;拿了一些offer&#xff0c;有阿里&#xff0c;滴滴&#xff0c;快手&#xff0c;达达&#xff0c;得物等公司。面试的公司大部分都能过&#xff0c;所以这里给大家分享下自己的经验&#xff0c;也给自己做个归档&…

新书推荐:《AIGC未来已来——迈向通用人工智能时代》

新书推荐&#xff1a;《AIGC未来已来——迈向通用人工智能时代》 导言 AIGC为何引发关注&#xff1f; ChatGPT会成为人工智能的拐点吗&#xff1f; GPT-4未来已来&#xff0c;奇点时刻该如何面对&#xff1f; 人类的创新能力会被AIGC替代吗&#xff1f; 当下有哪些典型的AIGC变…

单片机GD32F303RCT6 (Macos环境)开发 (二十)—— 光感芯片veml7700的使用

光感芯片veml7700的使用 1、veml有7个寄存器&#xff0c;每个十六位&#xff0c;见图。 00是config寄存器&#xff0c; 01 02 是中断设置的阈值 03是节能模式的设置 04 是得到的光的亮度值 05是得到的data of whole WHITE 06是中断设置值。 2、我们只测试得到光的亮度值&…