C++使用数组来实现哈夫曼树

 更新时间:2022年05月27日 10:52:21   作者:对象new不出来  
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近

写在前面

哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的路径长度是从树根到每一结点的路径长度之和,记为WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln),N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。可以证明霍夫曼树的WPL是最小的;但是今天,咱们不谈哈夫曼编码,就只用结构体配合数组来完成哈夫曼树的生成,目标是得到正确的所有权值的和。

构造思想

以字符的使用频率做权构建一棵哈夫曼树,然后利用哈夫曼树对字符进行编码,俗称哈夫曼编码。具体来讲,是将所要编码的字符作为叶子结点,该字符在文件中的使用频率作为叶子结点的权值,以自底向上的方式、通过执行n-1次的“合并”运算后构造出最终所要求的树,即哈夫曼树,它的核心思想是让权值大的叶子离根最近。每次从树的集合中取出双亲为0且权值最小的两棵树作为左、右子树,构造一棵新树,新树根结点的权值为其左右孩子结点权之和,将新树插入到树的集合中。

算法设计

步骤1:确定合适的数据结构。

步骤2:初始化。构造n棵结点为n个字符的单结点树集合F={T1,T2,…, Tn},每棵树中只有一个带权的根结点,权值为该字符的使用频率;

步骤3:如果F中只剩下一棵树,则哈夫曼树构造成功,转步骤6;否则,从集合F中取出双亲为0且权值最小的两棵树Ti和Tj,将它们合并成一棵新树Zk,新树以Ti为左儿子, Tj为右儿子(反之也可以)。新树Zk的根结点的权值为Ti与Tj的权值之和;

步骤4:从集合F中删去Ti、Tj,加入Zk;

步骤5:重复步骤3和4;

步骤6:从叶子结点到根结点逆向求出每个字符的哈夫曼编码(约定左分支表示字符“0”,右分支表示字符“1”)。则从根结点到叶子结点路径上的分支字符组成的字符串即为叶子字符的哈夫曼编码。算法结束。

构造实例

已知某系统在通信联络中只可能出现8种字符,分别为a,b,c,d,e,f,g,h,其使用频率分别为0.05,0.29,0.07,0.08,0.14,0.23,0.03,0.11,试设计哈夫曼编码。设权w=(5,29,7,8,14,23,3,11),n=8,按哈夫曼算法的设计步骤构造一棵哈夫曼编码树,具体过程如下:

理解代码

源码:

#include <iostream>
using namespace std;
#define N 8
struct Node 
{
	char L;//字母
	int K;//权值
	bool IsRoot;//是否为根结点
	int Lc, Rc;//左右子树
	Node(char l = '/0', int k = 0, bool isRoot = false) {
		L = l; K = k; IsRoot = isRoot; Lc = Rc = -1;
	}
};
Node A[N + N - 1] = { Node('a',5,true) , Node('b',29,true), Node('c',7,true), Node('d',8,true),
				      Node('e',14,true), Node('f',23,true), Node('g',3,true), Node('h',11,true) };
void FindMin(Node A[], int &min1, int &min2,int num)
{
	min1 = num - 1;
	for (int i = 0; i < num; i++) {
		if (A[i].IsRoot && A[i].K < A[min1].K) min1 = i;
	}
	min2 = num - 1;
	for (int i = 0; i < num; i++) {
		if (min1 != i && A[i].IsRoot &&A[i].K < A[min2].K) min2 = i;
	}
}
int main()
{
	int y = 1, num = N;
	int min1=0, min2=0;
	for (int i = 0; i < N; i++) {
		FindMin(A,min1,min2,num);
		A[num].K = A[min1].K + A[min2].K;
		A[num].Lc = min1;
		A[num].Rc = min2;
		A[num].IsRoot = true;
		A[min1].IsRoot = false;
		A[min2].IsRoot = false;
		A[num].L = 'h' + y;
		y++; num++;
	}
	cout << "最终权值为:" << A[14].K << endl;
}

确定结构体

首先我们创建结点 Node 结构体,并给他定义了五个属性,分别是字符、权值、布尔型根结点标志、以及左右子树。随后直接定义Node类型数组Node(char l,int k,bool isRoot),初始化Node A数组的长度为 N+N-1的原因是:选取两个最小权值生成新的结点,并把结点放入A数组中,相当于每两个结点会多生成一个节点,那么n个结点就会生成n-1个结点,总数就是n+n-1;我们依次把八个结点放入数组中,并把IsRoot置为true,初始结点均没有参加生成新节点,都是根结点。

循环找出最小值

将A传入找最小值的函数,初始num为N,随着新节点的生成,逐渐++,然后默认最小值为数组的最后一个元素,利用一重for循环遍历出最小值和次小值,这里注意,找出的最小值必须是根结点才可以,参加过生成新结点的都要把IsRoot设为false,次小值就是在不等于最小值的情况下找出最小值。

调用细节

主函数中利用一重for循环调用FindMin函数找出两个最小权值结点并默认放到数组的num位置上,即为最后一个位置;首先新节点的权值由两个最小权值相加,并把新节点的左右子树设为找到的两个最小权值结点,由于结构体数组默认IsRoot为false,所有要把新节点设为根结点,而把用过的结点取消根结点身份,L用来更换结点的字符,最后num++,y++都很好理解,经过七次新节点的生成,我们不难猜到A[14]的权值k为100;

调试试图

通过调试界面可以看到第一个生成的A[8]结点的左右子树是A[0]和A[6],而且权值正好是他们两个结点的权相加;

A[9]的k是新的两个最小根结点A[2]和A[8]组成的,可以确认权值没有问题,A[8]能参加新节点的合成说明我们成功的把他设置为了根结点,然后直接看最后一个结点的信息;

权值正确为100,是根节点,因为只有一个根结点,没法继续合成新节点,程序结束。

总结

哈夫曼在算法和数据结构中都挺重要的,只是不同场景下实现的方法和形式会有所不同,但就算千变万化也离不开最基础的“二合一”形式,我提出的这个哈夫曼是不难理解的,希望对大家有所帮助,共同进步!!!

到此这篇关于C++使用数组来实现哈夫曼树的文章就介绍到这了,更多相关C++哈夫曼树内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言如何写类实现教程示例

    C语言如何写类实现教程示例

    这篇文章主要为大家介绍了C语言如何写类的实现教程示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • C++中gSOAP的使用详解

    C++中gSOAP的使用详解

    这篇文章主要介绍了C++中gSOAP的使用详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2021-11-11
  • C++超详细讲解RTTI和cast运算符的使用

    C++超详细讲解RTTI和cast运算符的使用

    RTTI(Runtime Type Identification)是“运行时类型识别”的意思。C++引入这个机制是为了让程序在运行时能根据基类的指针或引用来获得该指针或引用所指的对象的实际类型,cast强制转换运算符是一种特殊的运算符,它把一种数据类型转换为另一种数据类型
    2022-08-08
  • QT基于TCP网络聊天室

    QT基于TCP网络聊天室

    这篇文章主要为大家详细介绍了QT基于TCP网络聊天室,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-08-08
  • 详解C语言中的字符串数组

    详解C语言中的字符串数组

    这篇文章主要介绍了C语言中的字符串数组,本文通过示例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-09-09
  • C++11中value category(值类别)及move semantics(移动语义)的介绍

    C++11中value category(值类别)及move semantics(移动语义)的介绍

    这篇文章主要给大家介绍了C++11中value category(值类别)及move semantics(移动语义)的介绍,文中介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-05-05
  • c/c++获取系统时间函数的方法示例

    c/c++获取系统时间函数的方法示例

    这篇文章主要介绍了c/c++获取系统时间函数的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • C语言实现经典扫雷小游戏的示例代码

    C语言实现经典扫雷小游戏的示例代码

    扫雷游戏是在一个指定的二维空间里,随机布置雷,把不是雷的位置都找出来,在你点一个位置的时候它会显示它周围全部雷的个数,根据这个线索去找 ,会更容易赢。本文将用C语言实现这一经典游戏,感兴趣的可以尝试一下
    2022-11-11
  • C语言+win32api写窗体应用程序

    C语言+win32api写窗体应用程序

    本文给大家分享的是个人使用纯C语言结合win32api制作窗体应用程序的代码,非常的简单,给需要的小伙伴参考下。
    2016-02-02
  • C++ read函数读入int整形数据

    C++ read函数读入int整形数据

    这篇文章主要介绍了C++ read函数读入int整形数据的相关资料,需要的朋友可以参考下
    2016-07-07

最新评论