1、基本概念

a、路径和路径长度

若在一棵树中存在着一个结点序列 k1,k2,……,kj, 使得 ki是ki+1 的双亲(1<=i<j),则称此结点序列是从 k1 到 kj 的路径。

从 k1 到 kj 所经过的分支数称为这两点之间的路径长度,它等于路径上的结点数减1.

b、结点的权和带权路径长度

在许多应用中,常常将树中的结点赋予一个有着某种意义的实数,我们称此实数为该结点的权,(如下面一个树中的蓝色数字表示结点的权)

结点的带权路径长度规定为从树根结点到该结点之间的路径长度与该结点上权的乘积。

c、树的带权路径长度

树的带权路径长度定义为树中所有叶子结点的带权路径长度之和,公式为:

其中,n表示叶子结点的数目,wi 和 li 分别表示叶子结点 ki 的权值和树根结点到 ki 之间的路径长度。

如下图中树的带权路径长度 WPL = 9 x 2 + 12 x 2 + 15 x 2 + 6 x 3 + 3 x 4 + 5 x 4  =  122

d、哈夫曼树

哈夫曼树又称最优二叉树。它是 n 个带权叶子结点构成的所有二叉树中,带权路径长度 WPL 最小的二叉树。

如下图为一哈夫曼树示意图。

2、构造哈夫曼树

假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:

(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);

(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;

(3)从森林中删除选取的两棵树,并将新树加入森林;

(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

如:对 下图中的六个带权叶子结点来构造一棵哈夫曼树,步骤如下:

 

注意:为了使得到的哈夫曼树的结构尽量唯一,通常规定生成的哈夫曼树中每个结点的左子树根结点的权小于等于右子树根结点的权。

具体算法如下:

/**
* 创建哈夫曼树
*/
PtrHuffman createHuffmanTree(ElemType arr[]){
PtrHuffman ptrArr[LENGTH];
PtrHuffman ptr,pRoot=NULL; for (int i = ; i < LENGTH; i++){ //初始化结构体指针数组,数组中每一个元素为一个结构体指针类型
ptr = (PtrHuffman)malloc(sizeof(HuffmanTreeNode));
ptr->data = arr[i];
ptr->left = ptr->right = NULL;
ptrArr[i] = ptr;
} for(i = ; i < LENGTH; i++){ //进行 n-1 次循环建立哈夫曼树
//k1表示森林中具有最小权值的树根结点的下标,k2为次最小的下标
int k1 = -, k2;
for(int j = ; j < LENGTH; j++){
if (ptrArr[j] != NULL && k1 == -){
k1 = j;
continue;
}
if (ptrArr[j] != NULL){
k2 = j;
break;
}
}
//将指针数组中的指针指向的最小值赋值给索引号为k1的,次小值赋值给索引号为k2的
for (j = k2; j < LENGTH; j++){
if(ptrArr[j] != NULL){
if(ptrArr[j]->data < ptrArr[k1]->data){
k2 = k1;
k1 = j;
}else if(ptrArr[j]->data < ptrArr[k2]->data){
k2 = j;
}
}
}
//由最小权值树和次最小权值树建立一棵新树,pRoot指向树根结点
pRoot = (PtrHuffman)malloc(sizeof(HuffmanTreeNode));
pRoot->data = ptrArr[k1]->data + ptrArr[k2]->data;
pRoot->left = ptrArr[k1];
pRoot->right = ptrArr[k2]; ptrArr[k1] = pRoot; //将指向新树的指针赋给ptrArr指针数组中k1位置
ptrArr[k2] = NULL; //k2位置为空
} return pRoot;
}

3、哈夫曼编码

在电报通信中,电文是以二进制的0、1序列传送的,每个字符对应一个二进制编码,为了缩短电文的总长度,采用不等长编码方式,构造哈夫曼树,

将每个字符的出现频率作为字符结点的权值赋予叶子结点,每个分支结点的左右分支分别用0和1编码,从树根结点到每个叶子结点的路径上

所经分支的0、1编码序列等于该叶子结点的二进制编码。如上文所示的哈夫曼编码如下:

a 的编码为:00

b 的编码为:01

c 的编码为:100

d 的编码为:1010

e 的编码为:1011

f 的编码为:11

4、哈夫曼树的操作运算

以上文的哈夫曼树作为具体实例,用详细的程序展示哈夫曼树的操作运算:

/** 哈夫曼树编码 **/
#include<stdio.h>
#include<stdlib.h>
#define LENGTH 6 typedef int ElemType; typedef struct HuffmanTreeNode{
ElemType data; //哈夫曼树中节点的权值
struct HuffmanTreeNode* left;
struct HuffmanTreeNode* right;
}HuffmanTreeNode,*PtrHuffman; /**
* 创建哈夫曼树
*/
PtrHuffman createHuffmanTree(ElemType arr[]){
PtrHuffman ptrArr[LENGTH];
PtrHuffman ptr,pRoot=NULL; for (int i = ; i < LENGTH; i++){ //初始化结构体指针数组,数组中每一个元素为一个结构体指针类型
ptr = (PtrHuffman)malloc(sizeof(HuffmanTreeNode));
ptr->data = arr[i];
ptr->left = ptr->right = NULL;
ptrArr[i] = ptr;
} for(i = ; i < LENGTH; i++){ //进行 n-1 次循环建立哈夫曼树
//k1表示森林中具有最小权值的树根结点的下标,k2为次最小的下标
int k1 = -, k2;
for(int j = ; j < LENGTH; j++){
if (ptrArr[j] != NULL && k1 == -){
k1 = j;
continue;
}
if (ptrArr[j] != NULL){
k2 = j;
break;
}
}
//将指针数组中的指针指向的最小值赋值给索引号为k1的,次小值赋值给索引号为k2的
for (j = k2; j < LENGTH; j++){
if(ptrArr[j] != NULL){
if(ptrArr[j]->data < ptrArr[k1]->data){
k2 = k1;
k1 = j;
}else if(ptrArr[j]->data < ptrArr[k2]->data){
k2 = j;
}
}
}
//由最小权值树和次最小权值树建立一棵新树,pRoot指向树根结点
pRoot = (PtrHuffman)malloc(sizeof(HuffmanTreeNode));
pRoot->data = ptrArr[k1]->data + ptrArr[k2]->data;
pRoot->left = ptrArr[k1];
pRoot->right = ptrArr[k2]; ptrArr[k1] = pRoot; //将指向新树的指针赋给ptrArr指针数组中k1位置
ptrArr[k2] = NULL; //k2位置为空
} return pRoot;
} /**
* 计算哈夫曼树带权路径长度WPL
*/
ElemType calculateWeightLength(PtrHuffman &ptrTree,int len){
if(ptrTree==NULL){ //空树返回0
return ;
}else{
if(ptrTree->left==NULL && ptrTree->right==NULL){ //访问到叶子节点
return ptrTree->data * len;
}else{
return calculateWeightLength(ptrTree->left,len+) + calculateWeightLength(ptrTree->right,len+); //向下递归计算
}
}
} /**
* 哈夫曼树编码(叶子节点按中序方式依次打印其编码)
*/
void HuffmanCoding(PtrHuffman &ptrTree,int len){
//静态局部变量相当于全局变量(只是只有在这个函数中能访问,但是生命周期是和全局变量差不多的)函数退出之后变量还在,而且只在第一次进入的时候做初始化,以后会跳过初始化语句,保留原来的值
static int arr[];
if(ptrTree != NULL){
if(ptrTree->left==NULL && ptrTree->right==NULL){
printf("结点权值为%d的编码: ", ptrTree->data);
for(int i = ; i < len; i++){
printf("%d", arr[i]);
}
printf("\n");
}else{
arr[len] = ;
HuffmanCoding(ptrTree->left,len+);
arr[len] = ;
HuffmanCoding(ptrTree->right,len+);
}
}
} /**
* 打印哈夫曼树中各个节点的孩子节点
* 若为叶子节点,则只显示提示信息
* @param node 需要显示孩子节点的父节点
*/
void printHuffmanTreeChildNode(PtrHuffman node){
if(node->left == NULL && node->right == NULL){
printf("x=%d是哈夫曼树中的叶子节点",node->data);
printf("\n\n");
return;
}
if(node->left != NULL){
printf("x=%d在哈夫曼树中的左孩子节点是lchild=%d",node->data,node->left->data);
printf("\n");
}
if(node->right != NULL){
printf("x=%d在哈夫曼树中的右孩子节点是rchild=%d",node->data,node->right->data);
printf("\n");
}
printf("\n");
} /**
* 中序打印哈夫曼树的节点
*/
void midOrderprintHuffmanTreeNode(PtrHuffman &pRoot){
if(pRoot==NULL){
return;
}else{
midOrderprintHuffmanTreeNode(pRoot->left);
printf("%d ",pRoot->data);
midOrderprintHuffmanTreeNode(pRoot->right);
}
} /**
* 先序打印哈夫曼树的节点
*/
void PreOrderprintHuffmanTreeNode(PtrHuffman &pRoot){
if(pRoot==NULL){
return;
}else{
printHuffmanTreeChildNode(pRoot); //依次打印哈夫曼树中各个节点的孩子节点
PreOrderprintHuffmanTreeNode(pRoot->left);
PreOrderprintHuffmanTreeNode(pRoot->right);
}
} /**
* 测试程序入口
*/
int main(){
ElemType arr[] = {,,,,,};
PtrHuffman pRoot = createHuffmanTree(arr); //返回指向哈夫曼树根节点的指针 printf("==========中序打印哈夫曼树节点数据==========\n");
midOrderprintHuffmanTreeNode(pRoot);
printf("\n\n"); printf("==========先序打印哈夫曼树节点关系==========\n");
PreOrderprintHuffmanTreeNode(pRoot); printf("==========计算带权路径长度==========\n");
printf("WeightLength=%d\n",calculateWeightLength(pRoot,));
printf("\n"); printf("==========各节点的哈夫曼树编码==========\n");
HuffmanCoding(pRoot,); fprintf(stdout,"\n"); return ;
}

运行结果截图:

数据结构之C语言实现哈夫曼树的更多相关文章

  1. 数据结构图文解析之:哈夫曼树与哈夫曼编码详解及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  2. 数据结构-二叉树(6)哈夫曼树(Huffman树)/最优二叉树

    树的路径长度是从树根到每一个结点的路径长度(经过的边数)之和. n个结点的一般二叉树,为完全二叉树时取最小路径长度PL=0+1+1+2+2+2+2+… 带权路径长度=根结点到任意结点的路径长度*该结点 ...

  3. Java数据结构(十二)—— 霍夫曼树及霍夫曼编码

    霍夫曼树 基本介绍和创建 基本介绍 又称哈夫曼树,赫夫曼树 给定n个权值作为n个叶子节点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称为最优二叉树 霍夫曼树是带权路径长度最短的树,权值较 ...

  4. 哈夫曼树(一)之 C语言详解

    本章介绍哈夫曼树.和以往一样,本文会先对哈夫曼树的理论知识进行简单介绍,然后给出C语言的实现.后续再分别给出C++和Java版本的实现:实现的语言虽不同,但是原理如出一辙,选择其中之一进行了解即可.若 ...

  5. 哈夫曼树(二)之 C++详解

    上一章介绍了哈夫曼树的基本概念,并通过C语言实现了哈夫曼树.本章是哈夫曼树的C++实现. 目录 1. 哈夫曼树的介绍 2. 哈夫曼树的图文解析 3. 哈夫曼树的基本操作 4. 哈夫曼树的完整源码 转载 ...

  6. 哈夫曼树(三)之 Java详解

    前面分别通过C和C++实现了哈夫曼树,本章给出哈夫曼树的java版本. 目录 1. 哈夫曼树的介绍 2. 哈夫曼树的图文解析 3. 哈夫曼树的基本操作 4. 哈夫曼树的完整源码 转载请注明出处:htt ...

  7. C语言数据结构之哈夫曼树及哈夫曼编码的实现

    代码清单如下: #pragma once #include<stdio.h> #include"stdlib.h" #include <string.h> ...

  8. Android版数据结构与算法(七):赫夫曼树

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 近期忙着新版本的开发,此外正在回顾C语言,大部分时间没放在数据结构与算法的整理上,所以更新有点慢了,不过既然写了就肯定尽力将这部分完全整理好分享出 ...

  9. 6-9-哈夫曼树(HuffmanTree)-树和二叉树-第6章-《数据结构》课本源码-严蔚敏吴伟民版

    课本源码部分 第6章  树和二叉树 - 哈夫曼树(HuffmanTree) ——<数据结构>-严蔚敏.吴伟民版        源码使用说明  链接☛☛☛ <数据结构-C语言版> ...

随机推荐

  1. 新手入门Underscore.js 中文(template)

    Underscore.js是一个很精干的库,压缩后只有4KB.它提供了几十种函数式编程的方法,弥补了标准库的不足,大大方便了javaScript的编程.MVC框架Backbone.js就将这个库作为自 ...

  2. Atitit mac os 版本 新特性 attilax大总结

    Atitit mac os 版本 新特性 attilax大总结 1. Macos概述1 2. 早期2 2.1. Macintosh OS (系统 1.0)  1984年2 2.2. Mac OS 7. ...

  3. Atitit.软件兼容性原理与实践 v5 qa2.docx

    Atitit.软件兼容性原理与实践   v5 qa2.docx 1. Keyword2 2. 提升兼容性的原则2 2.1. What 与how 分离2 2.2. 老人老办法,新人新办法,只新增,少修改 ...

  4. 【代码笔记】iOS-UILable高度自适应(sizeWithFont)

    一,代码. - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. ...

  5. jquery双向列表选择器DIV模拟版

    前段时间项目需要用到双向列表选择,想直接用select,结果发现某些样式不支持,只好用div模拟了以下,功能基本实现能用了,需要其他功能自己加上,譬如列表里展示多列数据等. select版链接:htt ...

  6. Json对象与Json字符串互转(4种转换方式)

    Json字符与Json对象的相互转换方式有很多,接下来将为大家一一介绍下,感兴趣的朋友可以参考下哈,希望可以帮助到你 1>jQuery插件支持的转换方式: 复制代码代码如下: $.parseJS ...

  7. java、easyui-combotree树形下拉选择框

    最近一直在研究这个树形的下拉选择框,感觉非常的有用,现在整理下来供大家使用: 首先数据库的表架构设计和三级菜单联动的表结构是一样,(父子关系) 1.下面我们用hibernate建一下对应的额实体类: ...

  8. iOS关于通知传值Bool类型的注意点

    比如: [[NSNotificationCenter defaultCenter] postNotificationName:@"Notification_Name" object ...

  9. 无法解析指定对象的 TargetProperty (UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)“的异常解决

    最近在写动画的时候做一个倒计时的效果,就是数字从大到小的一个动画,但是当我设置要new PropertyPath("XXXXXXX")的时候却报了标题的异常,各种报错.百度了好久也 ...

  10. Ubuntu 16.04下Linux驱动编写第一步

    源码环境的搭建 Install源码 >sudo apt-cache search linux-source linux-source - Linux kernel source with Ubu ...