通识哈夫曼树及其应用,一起来构造属于自己的哈夫曼树

article/2025/8/30 4:52:17

 

1.哈夫曼树的背景

        哈夫曼(霍夫曼、赫夫曼)David Albert Huffman(August9,1925-October7,1999)。计算机科学的先驱,以他的哈夫曼编码闻名,在他的一生中,对于有限状态自动机,开关电路,异步过程和信号设计有杰出的贡献。

        他发明的Huffman编码能够使我们通常的数据传输数量减少到最小。这个编码的发明和这个算法一样十分引人入胜。1950年,Huffman在MlT的信息理论与编码研究生班学习。Robert Fano教授让学生们自己决定是参加期末考试还是做一个大作业。而Huffmani选择了后者,原因很简单,因为解决一个大作业可能比期未考试更容易通过。这个大作业促使了Huffman算法的诞生。

        离开MIT后,Huffman来到Jniversity of California的计算机系任教,并为此系的学术付了许多杰出的工作。而他的算法也广泛应用于传真机,图象压缩和计算机安全领域。但是Huffman却从未为此算法申请过专利或其它相关能够为他带来经济利益的东西,他将他全部的精力放在教学上,以他自己的话来说,"我所要带来的就是我的学生。“

 

 看一眼就知道,这是大师无疑啦

2.哈夫曼树的基本概念

先引入例题:

编程:将学生的百分制成绩转换为五分制成绩

<60 : E

60~69 : D

70~79 : C

80~89 : B

90~100 : A

以往我们可以使用if-else语句嵌套或者switch分支语句实现

if(score < 60)grade = 'E';
else if(score < 70)grade = 'D';
else if(score < 80)grade = 'C';
else if(score < 90)grade = 'B';
elsegrade = 'A';

 

应用哈夫曼树的概念

假设学生的成绩数据共10000个:

则5%的数据需1次比较,15%的数据需2次比较,40%的数据需3次比较,40%的数据需4次比较,因此10000个数据比较的次数为:10000(1×5%+2×15%+3×40%+4×10%)=31500次

但是用哈夫曼树来比较:

 

 哈夫曼树所以又叫最优二叉树

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

结点的路径长度:两结点间路径上的分支数。

 

 树的路径长度:从树根到每一个结点的路径长度之和。记作:TL

上面题目的路径之和:0+1+1+2+2+3+3+4+4 = 20

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

(weight):将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权

结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积。

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

记作:

Weighted Path Length

:有4个结点a,b,G,d,权值分别为7,5,2,4,

构造以此4个结点为叶子结点的二叉树:

 

 WL = 2 * 7 + 2 * 5 + 2 * 2 + 2 * 4 = 36

 

 WL = 2 * 1 + 4 * 2 + 7 * 3 + 5 * 3 = 46

哈夫曼树最优树

带权路径长度(WPL)最短的树

“带权路径长度最短”是在“度相同”的树中比较而得的结果,因此有最优二叉树、最优三叉树之称等等。

因为构造这种树的算法是由哈夫曼教授于1952年提出的,所以被称为哈夫曼树,相应的算法称为哈夫曼算法。

3.哈夫曼树的构造算法

哈夫曼树中权越大的叶子离根越近

贪心算法:构造哈夫曼树时首先选择权值小的叶子结点

哈夫曼算法(构造哈夫曼树的方法)

  1. 根据石>给定的权值{w,%,w}构成2棵二叉树的森林

    F={T1,T2,...,Tn},其中T只有一个带权为wi的根结点。

构造森林全是根

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

选用两小造新树

  1. 在F中删除这两棵树,同时将新得到的二叉树加入森林中。

删除两小添新人

  1. 重复步骤2和3,直到森林中只有一棵树为止,这棵树即为哈夫曼树

哈夫曼算法口诀

1、构造森林全是根;

2、选用两小造新树;

3、删除两小添新人;

4、重复2、3剩单根。

 

 Attention

  1. 哈夫曼树的结点的度数为0或2,没有度为1的结点。

  2. 包含n个叶子结点的哈夫曼树中共有2n - 1个结点。

例题

 

 

 总结

  1. 在哈夫曼算法中,初始时有n棵二叉树,要经过n - 1次合并最终形成哈夫曼树。

  2. 经过n - 1次合并产生n - 1个新结点,且这n - 1个新节点都是具有两个孩子的分支结点。

  3. 可见:哈夫曼树中共有2n - 1个结点,且其所有分支结点的度均不为1。

4.哈夫曼树构造算法的实现

1.采用顺序存储结构(一维结构数组)

结点类型定义

typedef struct
{int weight;int parent, lch, rch;
}HTNode,*HuffmanTree;

 

 哈夫曼树中共有2n-1个结点,不用0下标,数组大小为2n。

哈夫曼树构造算法

  1. 初始化HT[1.....2n-1]:lch = rch = parent = 0;

void CreateHuffmanTree(HuffmanTree HT, int n)
{//构造哈夫曼树——哈夫曼算法if(n <= 1) return;m = 2 * n - 1;//数组共2*n-1个元素HT = new HTNode[m + 1];//0号单元没有使用,HT[m]表示根节点for(int i = 1;i <= m; ++i)//将2n-1个元素的lch、rch、parent置为0{HT[i].lch = 0;HT[i].rch = 0;HT[i].parent = 0;}for(int i = 1;i <= 0; ++i){cin >> HT[i].weight;//输入前n个元素的weight值}//初始化结束,下面开始建立哈夫曼树
}
  1. 输入初始n个叶子结点:置HT[1n]的weight值;

  2. 进行一下n-1次合并,依次产生n-1个结点HT[i],i = n+1......2n-1;

    a)在HT[1....i-1]中选两个未被选过(从parent==0的结点中选)的weight最小的两个结点HT[s1]和HT[s2],s1、s2为两个最小结点下标;

    b)修改HT[s1]和HT[s2]的parent值:HT[s1].parent = i;HT[s2].parent = i;

    c)修改新产生的HT[i]:HT[i].weight = HT[s1].weight + HT[s2].weight;HT[i].lch = s1; HT[i].rch = s2;

//续算法
for(int i = n+1;i <= m; i++)//合并产生n-1个结点——构造Huffman树
{Select(HT, i-1, s1, s2);//在HT[k](1 ≤ k ≤ i1)中选择其双亲域为0//且权值最小的结点,并返回它们在HT中的序号s1和s2HT[s1].parent = i;HT[s2].parent = i;//表示从F中删除s1,s2HT[i].lch = s1;HT[i].rch = s2;//s1和s2分别作为i的左右孩子HT[i].weight = HT[s1].weight + HT[s2].weight;//i的权值为左右孩子权值之和
}

5.哈夫曼编码

在远程通讯中,要将带传字符转换成由二进制的字符串

设要传送的字符为:ABACCDA

若编码为:A——00 B——01 C——10 D——11

ABACCDA——00010010101100

若将编码设计为长度不等的二进制编码,即让待传字符串中出现次数较多的字符采用尽可能短的编码,则转换的二进制字符串便可能减少。

 

 不能有重码,不然会造成代码歧义。

关键:要设计长度不等的编码,则必须使任一字符的编码都不是另一个字符的编码的前缀

方法

  1. 统计字符集中每个字符在电文中出现的平均概率(概率越大,要求编码越短);

  2. 利用哈夫曼树的特点:权值越大的叶子离根越近;将每个字符的概率值作为权值,构造哈夫曼树。则概率越大的结点,路径越短。

  3. 在哈夫曼树的每个分支上标上0或1:

    结点的左分支标0,右分支标1

    把从根到每个叶子的路径上的符号连接起来,作为该叶子代表的字符的编码

例题

 

 两个问题

  1. 为什么哈夫曼编码能够保证是前缀编码? 因为没有一片树叶是另一片树叶的祖先,所以每个叶结点的编码就不可能是其它叶结点编码的前缀

  2. 为什么哈夫曼编码能够保证字符编码总长最短?

    因为哈夫曼树的带权路径长度最短,故字符编码的总长最短。

6.哈夫曼编码的算法实现

void CreateHuffmanCode(HuffmanTree HT, HuffmanCode &HC, int n)
{//从叶子结点到根逆向求每个字符的哈夫曼编码,存储在编码表HC中HC = new char*[n+1];//分配n个字符编码的头指针矢量cd = new char [n];//分配临时存放编码的动态数组空间ch[n-1] = '\0';//编码结束符for(int i = 1;i <= n; ++i){start = n - 1;c = i;f = HT[i].parent;while(f!=0){//从叶结点开始向上回溯,直到根节点--start;//回溯一次start向前指向的一个位置if(HT(f).lchild == c)cd[start] = '0';//结点c是f的左孩子,则生成代码0elsecd[start] = '1';//结点c是f的右孩子,则生成代码1c = f;f = HT[f].parent;//继续向上回溯}HC[i] = new char[n - start];//为第i个字符编码从临时空间cd复制到HC的当前行中strcpy(HC[i], &cd[start]);//将求得的编码从临时空间cd复制到HC的当前行中}delete cd;//释放临时空间
}//CreateHuffanCode

7.文件的编码和解码

  1. 编码: 输入各字符及其权值 构造哈夫曼树一HT[i] 进行哈夫曼编码HC[i] 查HC[i],得到各字符的哈夫曼编码

  2. 解码: 构造哈夫曼树 依次读入二进制码 读入0,则走向左孩子;读入1,则走向右孩子 一旦到达某叶子时,即可译出字符 然后再从根出发继续译码,指导结束

 

 

 构建哈夫曼树HT

求出原码报文OC

 

8.C++代码实现

#define _CRT_SECURE_NO_WARNINGS
#include <map>
#include <cmath>
#include <vector>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;typedef struct binaryTree {int node = -1;struct binaryTree* lchild = NULL, * rchild = NULL;
}Tree, * TreePointer;void inputElement();
bool compare(int a, int b);
TreePointer myPopArrEleToTree();
int myPopArr();
TreePointer myPopMap(int iindex);
void createSubTree();
void joinNumToSubTree();
void joinSubTreeToNum();
void joinSubTreeToSubTree();
void xin(TreePointer& T);vector<int> arr;  //用于存储当前所有元素,永远是按绝对值从小到大排序,正常元素为正数,如果是子树为负数  如队列{2,-3 5 -6 7.8} 
map<int, TreePointer> treeMap;  //用于存放子树int main(void) {inputElement();while (arr.size()>2) {  //运行到最后只剩两个元素,执行后会生成最后一个树,这个树就是我们要的答案if (arr.size() >= 2 && arr[0] > 0 && arr[1] > 0)createSubTree();//如果两个元素是,一个子树,一个数字if (arr.size() >= 2 && arr[0] < 0 && arr[1]>0)joinSubTreeToNum();if (arr.size() >= 2 && arr[0] > 0 && arr[1] < 0) joinNumToSubTree();if (arr.size() >= 2 && arr[0] < 0 && arr[1] < 0) joinSubTreeToSubTree();}cout << "result: ";for (auto t : treeMap) xin(t.second);return 0;
}//输入元素,输入-1退出
void inputElement() {int in;while (1) {cin >> in;if (in == -1) break;arr.push_back(in);}sort(arr.begin(), arr.end(), compare);
}//比较器
bool compare(int a, int b) {return abs(a) < abs(b);
}//获取最小的值并且获取后删除,给树赋值后返回
TreePointer myPopArrEleToTree() {TreePointer T = new Tree;T->node = arr[0];arr.erase(arr.begin());return T;
}//过去arr的第一个元素,并且删除第一个元素
int myPopArr() {int i = arr[0];arr.erase(arr.begin());return i;
}//获取treeMap中指定的子树,在treeMap中删除这个子树,并返回这个子树
TreePointer myPopMap(int index) {TreePointer T = treeMap[index];treeMap.erase(index);return T;
}//添加元素
void addToArr(int i) {arr.push_back(i);sort(arr.begin(), arr.end(), compare);
}//挨着的如果是两个数字则创建一个子树,并放到treeMap中
void createSubTree() {TreePointer T = new Tree;T->lchild = myPopArrEleToTree();T->rchild = myPopArrEleToTree();treeMap[-(T->lchild->node + T->rchild->node)] = T;addToArr(-(T->lchild->node + T->rchild->node));
}//挨着的如果是一个子树与数字创建一个子树,并放到treeMap中
void joinSubTreeToNum() {TreePointer T = new Tree;int weight = myPopArr();T->lchild = myPopMap(weight);T->rchild = myPopArrEleToTree();treeMap[-(-weight + T->rchild->node)] = T;addToArr(-(-weight + T->rchild->node));
}//挨着的如果是一个数字与子树创建一个子树,并放到treeMap中
void joinNumToSubTree() {TreePointer T = new Tree;T->rchild = myPopArrEleToTree();int weight = myPopArr();T->lchild = myPopMap(weight);treeMap[-(-weight + T->rchild->node)] = T;addToArr(-(-weight + T->rchild->node));
}//挨着的如果是一个子树与子树创建一个子树,并放到treeMap中
void joinSubTreeToSubTree() {TreePointer T = new Tree;int weight1 = myPopArr(), weight2 = myPopArr();T->lchild = myPopMap(weight1);T->rchild = myPopMap(weight2);treeMap[-(-weight1 + (-weight2))] = T;addToArr(-(-weight1 + (-weight2)));
}string str = "";
void xin(TreePointer& T) {if (T) {if (T->node != -1)cout << str << " ";str += "0";xin(T->lchild);str = str.substr(0, str.size() - 1);str += "1";xin(T->rchild);str = str.substr(0, str.size() - 1);}
}

 


http://chatgpt.dhexx.cn/article/CgT7tqX1.shtml

相关文章

哈佛结构冯·诺依曼结构

哈佛结构是一种将程序指令存储和 数据存储分开的存储器结构。哈佛结构是 一种并行体系结构&#xff0c;它的主要特点是将程序和数据存储在不同的存储空间中&#xff0c;即程序存储器和数据存储器是两个独立的存储器&#xff0c;每个存储器独立编址、独立访问。 冯诺依曼结构也…

微型计算机之哈佛架构是什么?

“哈佛体系结构”指的是什么&#xff1f; 微型计算机处理命令和数据&#xff0c;但是在很久以前的微型计算机中&#xff0c;用命令和数据共享了一条总线。在这种情况下&#xff0c;CPU在读取指令时使用总线&#xff0c;因此无法访问数据&#xff0c;并且在读取指令结束后访问数…

冯诺依曼结构、哈佛结构、改进型哈佛结构

冯诺依曼结构 冯诺依曼结构&#xff0c;又称为普林斯顿体系结构&#xff0c;是一种将程序指令存储器和数据存储器合并在一起的存储器结构。取指令和取操作数都在同一总线上&#xff0c;通过分时复用的方式进行&#xff1b;缺点是在高速运行时&#xff0c;不能达到同时取指令和…

高级数据结构之赫夫曼树

思考两个问题 电报发送&#xff1a;二战的时候大家都知道那时候普遍会应用电报&#xff0c;如果让你来设计一个电报的发送编码你该如何设计呢&#xff1f; 电报加密后越短越好&#xff0c;发送快。破解难解码容易换加密树也要快可逆的 压缩算法&#xff1a;给你10000个字符&am…

ARM到底是冯诺依曼结构还是哈佛结构?

问题 嵌入式的学习中ARM处理器是主题&#xff0c;这些年产业界除了PC和服务器市场外&#xff0c;以手机、pad、家电控制等为代表的嵌入式领域都被ARM几乎垄断了。所以学习嵌入式处理器&#xff0c;其实等同于学习ARM。&#xff08;当然了&#xff0c;近两年RISC-V架构横空出世在…

冯诺依曼结构和哈佛结构的区别

冯诺依曼结构和哈佛结构的区别 1. 冯诺依曼结构&#xff1a; 说明&#xff1a; 一种将程序指令存储器和数据存储器合并在一起的存储器结构。程序指令存储地址和数据存储地址指向同一个存储器的不同物理位置&#xff0c;因此程序指令和数据的宽度相同。 冯诺依曼的计算机必须…

冯诺依曼与哈佛结构的区别

cortex M3,M4主要采用哈弗结构 个人理解&#xff1a;最主要的区别在于程序空间和数据空间是否是一体的&#xff0c;冯诺依曼结构数据空间和地址空间是不分开的&#xff0c;而哈佛结构数据空间和地址空间是分开的 哈弗结构的优势&#xff1a;如果采用流水线设计&#xff0…

冯氏结构、哈佛结构、超级哈佛结构之间的异同

冯.诺伊曼结构 1945年&#xff0c;冯.诺伊曼首先提出了“存储程序”的概念和二进制原理&#xff0c;后来&#xff0c;人们把利用这种概念和原理设计的电子计算机系统统称为“冯.诺伊曼型结构”计算机。冯.诺伊曼结构的处理器使用同一个存储器&#xff0c;经由同一个总线传输…

哈佛结构

数字信号处理一般需要较大的运算量和较高的运算速度&#xff0c;为了提高数据吞吐量&#xff0c;在数字信号处理器中大多采用哈佛结构&#xff0c;如下图所示 图 哈佛结构 与冯.诺曼结构处理器比较&#xff0c;哈佛结构处理器有两个明显的特点&#xff1a; 使用两个独立的存储…

冯诺依曼体系结构、哈佛体系结构与改进型哈佛结构之间的区别

1、冯诺依曼结构  冯诺依曼结构又称作普林斯顿体系结构&#xff08;Princetionarchitecture&#xff09;。  1945年&#xff0c;冯诺依曼首先提出了“存储程序”的概念和二进制原理&#xff0c;后来&#xff0c;人们把利用这种概念和原理设计的电子计算机系统统称为“冯诺依…

哈佛结构与冯诺依曼结构(含STM32系统结构解析)

存储器是微控制器的重要组成部分&#xff0c;不同类型的微控制器其采用的存储结构与容量不尽相同&#xff0c;但存储器的用途是相同的&#xff0c;用于存放程序和数据。微控制器中的存储结构有两种基本构成形式。 冯诺依曼结构 冯诺依曼结构也称普林斯顿结构&#xff0c;是一…

STM32属于哈佛结构还是冯诺依曼结构?

目录 01、冯诺依曼体系 02、哈佛体系 03、arm和哈佛、冯诺依曼的关系 04、实际芯片制造 现代的CPU基本上归为冯诺伊曼结构&#xff08;也成普林斯顿结构&#xff09;和哈佛结构。 冯洛伊曼结构就是我们所说的X86架构&#xff0c;而哈佛结构就是ARM架构。一个广泛用于桌面端…

哈佛体系结构

哈佛机&#xff1a;为数据和程序提供了格子独立的存储器。 程序计数器只指向程序存储器&#xff0c;而不指向数据存储器&#xff0c;这样的的后果是很难再哈佛机上编写出一个自修改的程序。独立的程序存储器和数据存储器为数字信号处理提供了较高的性能。结构如下图所示&#x…

哈佛结构和冯诺依曼结构?STM32属于哈佛结构还是冯诺依曼结构?

现代的CPU基本上归为冯诺伊曼结构&#xff08;也成普林斯顿结构&#xff09;和哈佛结构。 冯诺依曼体系 冯诺依曼体系结构图如下 冯诺依曼结构也称普林斯顿结构&#xff0c;是一种将程序指令存储器和数据存储器合并在一起的存储器结构。数据与指令都存储在同一存储区中&…

什么是冯诺依曼结构、哈佛结构、改进型哈佛结构?

冯诺依曼结构 冯诺依曼结构&#xff0c;又称为普林斯顿体系结构&#xff0c;是一种将程序指令存储器和数据存储器合并在一起的存储器结构。取指令和取操作数都在同一总线上&#xff0c;通过分时复用的方式进行&#xff1b;缺点是在高速运行时&#xff0c;不能达到同时取指令和取…

哈佛结构和冯诺依曼结构

已剪辑自: https://zhuanlan.zhihu.com/p/136748306 1946年&#xff0c;第一台计算机ENIAC诞生&#xff0c;人类进入计算机时代&#xff0c;后来&#xff0c;美籍匈牙利数学家&#xff1a;冯.诺依曼提出了计算机“存储程序”的计算机设计理念&#xff0c;即将计算机指令进行编码…

冯·诺依曼、哈佛、改进型哈佛体系结构解析

在如今的CPU中&#xff0c;由于Catch的存在&#xff0c;这些概念已经被模糊了。个人认为去区分他们并没有什么意义&#xff0c;仅作为知识点。 哈佛结构设计复杂&#xff0c;但效率高。冯诺依曼结构则比较简单&#xff0c;但也比较慢。CPU厂商为了提高处理速度&#xff0c;在C…

哈佛结构和冯·诺依曼结构

目录 一、哈佛结构 二、冯诺伊曼结构 三、哈佛结构和冯诺伊曼结构对比 一、哈佛结构 哈佛结构是一种将程序指令存储和数据存储分开的存储器结构。哈佛结构是一种并行体系结构&#xff0c;它的主要特点是将程序和数据存储在不同的存储空间中&#xff0c;即程序存储器和数据存…

哈佛架构和冯诺依曼架构

一、两种架构的介绍 1.哈佛结构是一种将程序指令的存储与数据的存储分开的存储器结构。首先&#xff0c;CPU在程序指令存储器中读取程序指令内容&#xff0c;解码后获得数据地址&#xff0c;然后在相应的数据存储器中读取数据&#xff0c;并进行下一步操作。指令存储和数据存储…

2021-05-28

嵌入式--深入理解单片机&#xff08;一&#xff09;单片机程序是如何运行起来的以及单片机的ROM和RAM 目录 一、两种处理器的结构体系 1、哈佛结构体系&#xff08;Harvard architecture&#xff09;2、冯诺依曼结构体系3、两种结构的总结 哈佛结构的优势冯诺依曼结构的优势当前…