数据结构之哈夫曼树

8.哈夫曼树

8.1 哈夫曼编码

哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种可变字长编码(VLC)方式

这种编码方法完全依据字符出现的概率来构造异字头的平均长度最短的码字, 因此有时也被称为最佳编码。

  • 基本原理

    有5个字符a b c d e要发送

    a出现10次 b: 5 c: 3 d:20 f:1

    如果发送的要快 就要保证编码尽量短,这样就要求频率高的编码长度短,频率低的编码长度可以长一点。

  • 基本步骤:

    • 计算字符频率:首先,统计每个字符在数据中出现的频率(次数)。

    • 构建哈夫曼树:根据字符的频率,使用自底向上的方法构建一棵哈夫曼树。在构建过程中,将 频率最低的两个节点合并为一个新的节点,其频率为两者之和,并将这两个字符分别作为新节 点的左右子节点。然后,将新节点加入到未处理的字符列表中,继续重复此过程,直到所有字 符都被合并到一个根节点下

      • 为什么选择最低的两个节点?

        • 因为频率最低路径也越长,编码长度也长

        • 通信原理里面可能少的比特数传输尽可能多的信息,以提高通信效率。将频率低的信号编码为较长的码字,可以使得频率高的信号占用较少的比特数,从而整体上减少传输的数据量

    • 生成编码:从根节点开始,为树中的每个字符生成一个唯一的编码

      对于每个非叶子节点,将 其左子节点的连接线标记为“0”,右子节点的连接线标记为“1”。字符的编码即为从根节点到该 字符叶子节点路径上的所有“0”和“1”的序列

特点:

  • 高效性:由于字符编码的长度与其在数据中出现的频率成反比,因此高频字符使用较短的编

    码,而低频字符使用较长的编码,从而实现了数据的高效压缩。

  • 唯一性:哈夫曼编码的码字是异前置码字,即任一码字不会是另一码字的前面部分(因为父节

    点是子节点的和),这使得各码字可以连在一起传送,中间不需另加隔离符号。

  • 不唯一性:虽然哈夫曼树和编码的带权路径长度是唯一的,但哈夫曼树和编码本身并不唯一。

    在构建哈夫曼树时,如果两个字符的频率相等,它们可以以不同的顺序合并,从而导致不同的

    哈夫曼树和编码。

  • 应用场景

  • 文件和数据压缩:哈夫曼编码可用于文本、图像、音频等各种类型文件的压缩,通过减少文件

    大小来节省存储空间。

  • 通信传输:在网络传输中,使用哈夫曼编码可以减小数据的大小,从而减少网络带宽的占用,

    加快数据传输速度。

  • 无损压缩文件格式:GZIP、PKZIP、PNG和JPEG等文件格式都使用哈夫曼编码对文件中的数

    据进行压缩。

  • 文本处理:在自然语言处理、文本挖掘等领域,哈夫曼编码可用于生成文本的索引、单词频率

    统计和特征向量表示等任务。

8.2 哈夫曼树

  • 定义

哈夫曼树,也称为最优二叉树或最小带权路径长度树,是一种特殊的二叉树结构,其主要特点是带权路 径长度最短

给定N个权值(频率/次数)作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,则 称这样的二叉树为最优二叉树,也称为哈夫曼树。

  • 带权路径长度(WPL)

树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶 结点的层数)。计算公式为WPL = Σ(Wi * Li),其中Wi表示叶子结点ki的权值,Li表示根结点到叶子结点 ki的路径长度

实际中权可能是金钱、时间成本等等

  • 构造哈夫曼树

哈夫曼树的构造过程是自底向上的,步骤:

示例:已知字符集{ a, b, c, d, e, f },若各字符出现的次数分别为{ 6, 3, 8, 2, 10, 4 }

  • 初始化:将给定的N个权值视为N棵只有根结点的二叉树(即叶子结点),这些二叉树构成初始森林。

    每个节点都当成一个树(树构成森林)

  • 选择合并:在森林中选取两棵根结点的权值最小的树作为左右子树,构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和

    找两森林中两个权值最小的树,即b(3)和d(2)为左右子树,根节点的权值是二者之和在这里插入图片描述

  • 更新森林:在初始森林中删除这两棵树,同时将新得到的二叉树加入森林中**(左小右大**)。

  • 重复操作:重复上述两个步骤,直到森林中只剩下一棵树为止。

再选出2个最小的数,选出了4(f)和5(上述步骤中组合后的父节点)

在这里插入图片描述

此时该森林中就有4棵树(a, c, e)以及上述图片中的树,权值分别为(6, 8, 10 )

再选出2个最小的数,选出了6(a)和8(c)

在这里插入图片描述

此时该森林中就有3棵树(e)以及上述图片中两棵树的树,权值分别为(10 ) 再选出2个最小的数,选出了9和10(e)

在这里插入图片描述

此时该森林中就只有有上述图片中两棵树的树

将两棵树合并为一棵树

在这里插入图片描述

这棵树便是哈夫曼树。

  • 哈夫曼编码

从根节点开始向下走往左为0,往右1。走到对应的字符的路径就是该字符的哈夫曼编码(左0右1)

在这里插入图片描述

最终的哈夫曼编码 左0 右1

高频字符使用较短的编码,而低频字符使用较长的编码

字符 权值 哈夫曼编码

a 6 00

b 3 1011

c 8 01

d 2 1010

e 10 11

f 4 100

#include <iostream>
#include <queue>
using namespace std;
// 哈夫曼树的节点(一个节点表示一棵树)
typedef struct  Node
{int weight; //权值struct  Node *left; //左孩子struct  Node *right;//右孩子
}TreeNode;// 哈夫曼树((通过树表示森林))
typedef struct  HumanTree
{// 树的根节点 通过这个根节点可以访问整棵树的所有节点Node *root;//下一个树,通过链表把树连起来变成森林struct HumanTree *nextTree;
}HumanTree;// 创建节点
Node *createNode(int weight)
{//分配内存Node *newNode = new Node{weight};// 初始化newNode->left =nullptr;newNode->right = nullptr;return newNode;
}// 创建哈夫曼树
HumanTree *createHumanTree(Node *root)
{// 变量名不能和类型名相同HumanTree *newTree = new HumanTree; // 初始化newTree->root = root;newTree->nextTree = nullptr;return newTree;
}//森林插入新树
void insertTree(HumanTree **tree, HumanTree *newtree)//第一个是森林 第二个是新树 因为森林会插入新树数量会修改,如果使用二级指针
{// 森林是空,森林就是新树if(!*tree){(*tree) = newtree;return;}// 中间节点指针 从第一颗树开始遍历HumanTree *current = (*tree);// 循环 使用current->nextTree和链表一样,如果是current到了null无法返回尾树了  while(current->nextTree){current = current->nextTree;}// 尾数处插入新树current->nextTree = newtree;}// 从森林中删除某棵树(并非真正删除,而是合并了)
void deleteTree(HumanTree **tree, HumanTree *deletetree)
{// 如果删除的是第一课树 头树if((*tree)->root->weight == deletetree->root->weight ){(*tree) =(*tree)->nextTree;delete deletetree;deletetree = nullptr;return;}// 不是第一颗树 遍历HumanTree *currentTree = (*tree);while(currentTree->nextTree){// 找到了要删除的树  currentTreeif(currentTree->nextTree->root->weight == deletetree->root->weight){// 更新森林currentTree->nextTree = currentTree->nextTree->nextTree;delete deletetree;deletetree = nullptr;return;}currentTree = currentTree->nextTree;}
}// 选择合并  在森林中选取两棵根结点的权值最小的树,构造一棵新的二叉树 会改变树的结构 用**
bool selectMerge(HumanTree **tree)
{// 空树或者只有一个节点if(!(*tree) || !(*tree)->nextTree){return false;}// 1. 找到权重最小的两棵树HumanTree *minTree1 = nullptr;//最小HumanTree  *minTree2 =nullptr;//第二小// 先从森林里找到前两棵来为最小的两棵树初始化if((*tree)->root->weight < (*tree)->nextTree->root->weight)//判断先两颗树谁打谁小{minTree1 = (*tree);minTree2 = (*tree)->nextTree;}else{minTree1 = (*tree)->nextTree;minTree2 = (*tree);}//   2.当前树(从第三棵树开始与前两棵书比较)   // 存储第三个节点HumanTree *current = (*tree)->nextTree->nextTree;// 比这个树都小while(current){// 比最小的小 之前最小的变成第二项,当前的变成最小if(current->root->weight < minTree1->root->weight){// 更新倒数第二小minTree2 = minTree1;// 更新最小minTree1 =  current;}// 比最二的小,最小的大 else if(current->root->weight < minTree2->root->weight && current->root->weight > minTree1->root->weight){// 更新倒数第二小minTree2 = current;}// 往后访问current = current->nextTree;}// 2. 合并这两棵树//  创建父节点 存储这两个节点(最小和第二小)TreeNode *newTreeNode =  createNode(minTree1->root->weight + minTree2->root->weight);//  创建新树 参数是新的父节点HumanTree *newTree = createHumanTree(newTreeNode);// 左小右大 将两棵树合并为一颗新树newTree->root->left = minTree1->root;newTree->root->right = minTree2->root;// 3. 将新树插入到森林insertTree(tree, newTree);//4. 删除原来的两棵树deleteTree(tree, minTree1);deleteTree(tree, minTree2);return true;
}// 打印哈夫曼编码
void printTree(TreeNode *p_code, string& code) // 传入节点   code是为其添加0 1
{// 空树if(!p_code){return;}//叶子节点,则打印字符和对应的编码if(p_code->left == nullptr && p_code->right == nullptr){cout << p_code->weight << ":"  << code << endl;}// 递归地遍历左右子树,并在当前编码的基础上添加'0'或'1'else{string leftNode = code + "0";string rightNode = code + "1";printTree( p_code->left, leftNode);printTree( p_code->right, rightNode);}
}// 清理函数(左右根)
void deintTree(TreeNode *node)
{if(!node ){return;}deintTree(node->left);deintTree(node->right);delete node;
}// 层次遍历(队列(一层一层遍历)
void levelPrint(HumanTree *p_tree)
{if(!p_tree){cout << "空树" << endl;return;}// 创建队列,存储树中每个节点 先进先出,符合层次遍历queue <TreeNode*>  q;// 先存储根节点q.push(p_tree->root);while(!q.empty()){// 保存根节点信息TreeNode *current = q.front();//一直保存根节点// 弹出根节点q.pop();// 访问当前节点cout.width(2);cout << current->weight << " ";// 如果左子节点存在,则加入队列if(current->left){q.push(current->left);}// 如果右子节点存在,则加入队列if(current->right){q.push(current->right);}}cout << endl;}// 主函数示例
int main()
{// 创建权值int weight[] = {6, 3, 8, 2, 10, 4};// 创建哈夫曼树HumanTree *newHumanTree  = nullptr;//  创建树节点for(int i = 0; i < sizeof(weight)/sizeof( weight[0]); i++){// 创建树并插入森林// insertTree插入树函数 newHumanTree取地址作为二级指针传入,是当前森林,createHumanTree是创建树// createHumanTree参数是节点createNode(weight[i])是创建节点后直接作为参数传入 createHumanTre中insertTree (&newHumanTree,createHumanTree(createNode(weight[i])));}// 选择合并while (selectMerge(&newHumanTree));//  // 层次遍历levelPrint(newHumanTree);// 打印哈夫曼编码string code = " ";printTree(newHumanTree->root, code);// 销毁树内节点deintTree(newHumanTree->root);// 销毁树delete newHumanTree;newHumanTree = nullptr;return 0;
}

根节点root是树的入口,通过它可以访问整棵树的所有其他节点*

找了一顿bug结果是 selectMerg if(!(*tree) || !(*tree)->nextTree)空树或者只有一个节点写错了写成 selectMerg if(!(*tree) || (*tree)->nextTree) 下次注意

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

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

相关文章

机器学习实操 第一部分 机器学习基础 第5章 支持向量机(SVM)

机器学习实操 第一部分 机器学习基础 第5章 支持向量机&#xff08;SVM&#xff09; 内容概要 第5章深入介绍了支持向量机&#xff08;SVM&#xff09;&#xff0c;这是一种功能强大且应用广泛的机器学习模型。SVM适用于线性或非线性分类、回归以及 novelty detection。本章详…

Webug4.0靶场通关笔记14- 第18关 文件上传之Nginx解析缺陷

目录 第18关 渗透实战 1.打开靶场 2.构造php脚本 3.源码分析 &#xff08;1&#xff09;客户端源码 &#xff08;2&#xff09;服务的源码 4.Nginx解析法渗透 &#xff08;1&#xff09;缺陷原因 &#xff08;2&#xff09;缺陷条件 &#xff08;3&#xff09;构造脚…

【QT】QT中的网络编程(TCP 和 UDP通信)

QT中的网络编程&#xff08;TCP 和 UDP通信&#xff09; 1.tcp1.1 tcp通信1.1.1 相比linux中tcp通信:1.1.2 QT中的tcp通信: 1.2 tcp通信流程1.2.1 服务器流程&#xff1a;1.2.1.1 示例代码1.2.1.2 现象 1.2.2 客户端流程&#xff1a;1.2.2.1 示例代码1.2.2.2 现象&#xff1a; …

架构思维:使用懒加载架构实现高性能读服务

文章目录 一、引言二、读服务的功能性需求三、两大基本设计原则1. 架构尽量不要分层2. 代码尽可能简单 四、实战方案&#xff1a;懒加载架构及其四大挑战五、改进思路六、总结与思考题 一、引言 在任何后台系统设计中&#xff0c;「读多写少」的业务场景占据主流&#xff1a;浏…

永磁同步电机控制算法--基于PI的位置伺服控制

一、原理介绍 永磁同步伺服系统是包含了电流环、速度环和位置环的三环控制系统。 伺服系统通过电流检测电路和光电编码器检测电动机三相绕组电流和转子位置θ&#xff0c;通过坐标变换&#xff0c;计算出转矩电流分量iq和励磁电流分量id。 位置信号指令与实际转子位置信号的差…

linux系统线程实现原理浅析

背景 对进程和线程的理解&#xff0c;之前一直都是凭一些零碎不完整的信息在理解&#xff1b; linux的进程和线程基本上一样&#xff0c;线程是轻量级进程&#xff0c;彼此有关联又独立。 得亏内核支持的好&#xff0c;写用户态程序可以不依赖于实现的理解&#xff0c;只需要…

MySQL连接报错处理:1130-host ... is not allowed to connect to this MySql server

在MySQL安装完成后&#xff0c;很多开发者会遇到这样一个问题&#xff1a; 错误代码 1130&#xff1a;host xxx.xxx.xxx.xxx is not allowed to connect to this MySql server 这个错误通常出现在你尝试通过远程工具&#xff08;如 Navicat、DBeaver 等&#xff09;连接 MySQL …

Linux系统之----进程控制

1.进程创建 进程创建部分由于就是fork函数&#xff0c;还有写时拷贝&#xff0c;在上一篇已经讲述过了&#xff0c;这里不在进行赘述&#xff0c;有疑问的读者可以前往上一篇博文《Linux系统--程序地址空间》中阅读&#xff01; 这里在多说一嘴写时拷贝吧 我们可以对比一下写…

Spring框架的设计目标,设计理念,和核心是什么 ?

Spring框架是一个为简化企业级应用开发而设计的开源框架&#xff0c;它提供了全面的基础设施支持&#xff0c;使得Java应用开发更加简单、快速和可维护。下面我将详细解释Spring框架的设计目标、设计理念以及核心组件。 设计目标 简化Java企业级应用开发&#xff1a;通过提供…

Red Hat6.4环境下搭建DNS服务器

DNS服务器&#xff08;Domain Name System Server&#xff09;是互联网中用于将域名&#xff08;如 www.example.com&#xff09;解析为IP地址&#xff08;如 192.0.2.1&#xff09;的服务器。它是互联网基础设施的重要组成部分&#xff0c;帮助用户通过易于记忆的域名访问网站…

Nginx核心功能 02

目录 Nginx代理技术核心概念 &#xff08;一&#xff09;正向代理&#xff08;Forward Proxy&#xff09; 1. 基本定义 2. 技术原理 3. 应用场景 &#xff08;二&#xff09;反向代理&#xff08;Reverse Proxy&#xff09; 1. 基本定义 2. 技术原理 3. 应用场景 一、…

关于Python:3. Python标准库和常用模块

1. os 和 sys&#xff08;系统编程基础&#xff09; 这两个模块是进行系统层面操作&#xff08;如文件管理、路径处理、环境变量访问等&#xff09;必不可少的工具。 os 模块 os 主要是用于与操作系统交互的&#xff0c;比如&#xff1a; 文件和目录操作 获取系统信息 运行…

Java基于SaaS模式多租户ERP系统源码

目录 一、系统概述 二、开发环境 三、系统功能介绍 一、系统概述 ERP&#xff0c;全称 Enterprise Resource Planning 即企业资源计划。是一种集成化的管理软件系统&#xff0c;它通过信息技术手段&#xff0c;将企业的各个业务流程和资源管理进行整合&#xff0c;以提高企业…

个人健康中枢的多元化AI网络革新与精准健康路径探析

引言 随着数字化转型的深入推进,个人健康中枢作为集成化健康管理系统,正在从传统的单一功能向多元化的AI驱动方向快速发展。在这一背景下,新兴网络硬件技术,特别是DPU(数据处理单元)和全光网络的出现,为个人健康中枢的革新提供了前所未有的机遇。本研究将深入探讨这些技…

AI跑得快,MCP来加速——模型计算平台在训练与推理中的硬核作用

AI跑得快,MCP来加速——模型计算平台在训练与推理中的硬核作用 一、引言:AI是“铁人三项”,但训练+推理常常“掉链子” 如今的人工智能系统越来越强,像ChatGPT、Stable Diffusion、Segment Anything等模型不断刷新技术天花板。但你是否也注意到: 明明模型设计得挺好,训练…

《MATLAB实战训练营:从入门到工业级应用》工程实用篇-自动驾驶初体验:车道线检测算法实战(MATLAB2016b版)

《MATLAB实战训练营&#xff1a;从入门到工业级应用》工程实用篇-&#x1f697; 自动驾驶初体验&#xff1a;车道线检测算法实战&#xff08;MATLAB2016b版&#xff09; 大家好&#xff01;今天我要带大家一起探索自动驾驶中一个非常基础但又至关重要的技术——车道线检测。我…

模型部署——cuda编程入门

CUDA中的线程与线程束 kernel是在device上线程中并行执行的函数&#xff0c;核函数用__global__符号声明&#xff0c;在调用时需要用<<<grid_size, block_size>>>来指定kernel要执行的线程数量。在CUDA中&#xff0c;每一个线程都要执行核函数&#xff0c;并…

WordPress不支持中文TAG标签出现404的解决方法

我们在后台编辑文章时输入中文标签会发现出现404的情况&#xff0c;其实中文TAG标签链接无法打开的原因是WordPress不支持中文的编码。那么解决的方法也很容易&#xff0c;只要改代码让WordPress能支持中文的编码形式&#xff0c;也就是UTF-8和GBK编码即可&#xff0c;无需用到…

金融信贷公司所需的技术和风控体系及其带来的价值

金融信贷公司的技术架构通过集成传统大型机系统与现代数据平台&#xff0c;能够有效支持金融信贷业务的运作&#xff0c;同时通过大数据、ETL、报表开发、数据仓库等技术为公司带来更高效的数据驱动决策、精准的风控分析和更灵活的业务支持。 一、公司技术架构 数据仓库架构&…

《AI大模型应知应会100篇》第43篇:大模型幻觉问题的识别与缓解方法

第43篇&#xff1a;大模型幻觉问题的识别与缓解方法 摘要 当AI系统自信满满地编造"量子计算机使用香蕉皮作为能源"这类荒谬结论时&#xff0c;我们不得不正视大模型的幻觉问题。本文通过15个真实案例解析、6种检测算法实现和3套工业级解决方案&#xff0c;带您掌握…