霍夫曼(Huffman)树创建算法详解之C语言版

一、Huffman树

霍夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,在众多领域中有着广泛的应用,尤其是信息编码。Huffman树的主要用处就是对数据进行编码,得到0-1码流,以实现快速传输,此编码也称为Huffman编码。本文主要给出了Huffman树的创建过程及相应的算法实现。
树的权路径长度是树上全部结点到树根的权路径长度之和。例如有如下一棵树,结点B、E和K的路径长度是1,C、D、F和G的路径长度是2,H和I的路径长度是3,J的路径长度是4,假设每个结点的权均是1,则该树的权路径长度为
WPL = 3×1+4×2+2×3+1×4 = 21
在这里插入图片描述
现在考虑二叉树,给每个树叶结点都赋予权值,则树叶的权值乘以路径长度就得到了每个树叶结点的权路径长度,所有树叶结点的权路径长度之和就是该二叉树的权路径长度。由于现在考虑的是有限结点的二叉树,因此由这些树叶结点所组成的全部二叉树中,一定存在一个权路径长度为最小的一棵,此树即为Huffman树
例如:如下图给出的三个叶子结点的权分别是1,2和3的二叉树:
在这里插入图片描述
每一棵二叉树的权路径长度分别为:
图(a):WPL = 1×2+2×2+3×2 = 12
图(b):WPL = 3×1+1×2+2×2 = 9
图(c):WPL = 2×1+1×2+3×2 = 10
图(d):WPL = 1×1+2×2+3×2 = 11
由此可以看出图(2)中的二叉树对应的WPL值最小,其为Huffman树。

二、Huffman树的创建过程示例

给定一组数据:E, A, B, C, B, A, E, D, B, C, E, E, E, D, C, E, B, A, B, C,根据此数据创建一棵Huffman树。
构建Huffman树的过程如下:
Step 1: 统计每个元素出现的频数(按照字母出现的次序统计):
在这里插入图片描述
Step 2:以这些频数作为每个数据的权,创建Huffman树。
在这里插入图片描述
**1)**找出最小的二个权,生成一个二叉树(左小右大),二个权之和5作为二叉树的树根,把权值2和3从权序列这个删除,将5加入,得到新的权:
在这里插入图片描述
**2)**找出当前权中最小的两个权,继续生成一棵二叉树(遇到权值相同,则单个权值优先):
在这里插入图片描述
**3)**找出当前权中最小的两个权,继续生成一棵二叉树:
在这里插入图片描述
**4)**找出当前权中最小的两个权,继续生成一棵二叉树:
在这里插入图片描述

至此,就得到了一棵Huffman树,该Huffman树的权路径长度为:
WPL = 6×2+4×2+5×2+2×3+3×3 = 45.

三、Huffman树的创建流程

前面给出了Huffman树的创建过程,为了方便实现创建Huffman树,引入了结构体数组,用来存储Huffman树结点信息,并用结构数组存储Huffman树全部的结点。则前述的Huffman树创建过程可以用数组实现如下:
准备工作:生成一个结构体数组,存储全部权,数组元素个数为权个数的2倍减1。并将每一个权对应结点的双亲、左子树、右子树的序号置为-1:
在这里插入图片描述
Step 1:找到权值(有背景色)最小的两个3和2相加得到5,存到下标为5的元素中,并将下标5的左子树的下标赋值为4,右子树下标赋值为1,将下标4和1中的双亲下标赋值为5.
在这里插入图片描述
Step 2:在剩下的权值(有背景色)中找到权值最小的两个5和4相加得到9,存到下标为6的元素中,并将下标6的左子树的下标赋值为3,右子树下标赋值为2,将下标3和2中的双亲下标赋值为6.
在这里插入图片描述
Step 3:在剩下的权值(有背景色)中找到权值最小的两个5和6相加得到11,存到下标为7的元素中,并将下标7的左子树的下标赋值为5,右子树下标赋值为0,将下标5和0中的双亲下标赋值为7.
在这里插入图片描述
Step 4:在剩下的权值(有背景色)中找到权值最小的两个9和11相加得到20,存到下标为8的元素中,并将下标8的左子树的下标赋值为6,右子树下标赋值为7,将下标6和7中的双亲下标赋值为8.
在这里插入图片描述

四、创建Huffman树算法的C程序

1.存储Huffman树结点的结构体

typedef struct HTree
{
	char cChar;
	int  Weight; 
	int  Parent;
	int  LChild;
	int  RChild;
}HuffmanTree;

2.创建Huffman树的算法之C程序

//存储字符及字符出现次数(权值)的结构体
typedef struct charStat
{
	char c;
	int  num;
}CharStat;

/*******************************************************************************
void HTreeCreation( CharStat *cStat, HuffmanTree *TreeNode, int codeNum )
功能:创建Huffman树
输入:cStat:存储统计结果的数组,每个元素包括2项,分别是字符、次数
      codeNum:不同字符的个数
输出:TreeNode:Huffman树的结点
返回值:无
********************************************************************************/
void HTreeCreation( CharStat *cStat, HuffmanTree *TreeNode, int codeNum )
{
	int i, j, k;
	int w1, w2, p1, p2;
	int nodeNum;
	nodeNum = 2 * codeNum - 1;

	for( i=0; i<nodeNum; i++ )//initial HuffmanTree
	{
		if( i < codeNum )
		{
			TreeNode[i].cChar  = cStat[i].c;
			TreeNode[i].Weight = cStat[i].num;
		}
		else
		{
			TreeNode[i].cChar  = '\0';
			TreeNode[i].Weight = 0;
		}
		TreeNode[i].LChild = BLANKFLAG;
		TreeNode[i].RChild = BLANKFLAG;
		TreeNode[i].Parent = BLANKFLAG;
		
	}
	//找最小的两个权生成一棵子树,存储到第一个非叶子结点,依此,直到只剩下一个结点
	for( j=codeNum; j<nodeNum; j++ )
	{
		w1 = 0xFFFFFFF;
		w2 = w1;
		p1 = p2 = BLANKFLAG;
		for( k=0; k<j; k++ )
		{
			if( TreeNode[k].Weight < w1 && TreeNode[k].Parent == BLANKFLAG )
			{
				w1 = TreeNode[k].Weight;
				p1 = k;
			}
		}
		for( k=0; k<j; k++ )
		{
			if( TreeNode[k].Weight < w2 && k != p1  && TreeNode[k].Parent == BLANKFLAG )
			{
				w2 = TreeNode[k].Weight;
				p2 = k;
			}
		}
		TreeNode[j].Weight  = w1 + w2;
		TreeNode[j].LChild  = p1;
		TreeNode[j].RChild  = p2;
		TreeNode[p1].Parent = j;
		TreeNode[p2].Parent = j;
	}
}//HTreeCreation

3.完整的测试代码
从键盘读入一个字符串,统计每个字符出现的次数,并据此创建Huffman树。

#include"stdio.h"
#include"string.h"

#define MAX_NODE 100
#define BLANKFLAG  -1

//存储从键盘或者文件读入的字符串。
typedef struct charLinkList
{
	char cChar;
	struct charLinkList *pNext;
}CharLinkList;

//存储字符及字符出现的次数(权值)
typedef struct charStat
{
	char c;
	int  num;
}CharStat;
//Huffman树的结点信息,其中前n个为叶子结点
typedef struct HTree
{
	char cChar;
	int  Weight; 
	int  Parent;
	int  LChild;
	int  RChild;
}HuffmanTree;

//获取字符串,并存储到以str为表头的单链表中
void getStr( CharLinkList &str, int &cNum );
//统计每个字符及出现的次数
void cStatInf( CharLinkList str, int cTotalNum, CharStat *cStat, int &codeNum );
//生成Huffman树
void HTreeCreation(  CharStat *cStat, HuffmanTree *TreeNode, int codeNum );

int main()
{
	int i;
	CharLinkList str, *p;
	CharStat     *cStat;
	int cTotalNum, codeNum;

	//从键盘获取待编码的字符串
	printf( "Please input a string which need to be encoded:\n" );
	getStr( str, cTotalNum );
	
	//统计待编码字符串中不同字符及其出现的次数
	cStat = new CharStat[ cTotalNum ];
	cStatInf( str, cTotalNum, cStat, codeNum );
	//向屏幕输出待编码字符串中的字符信息
	printf( "the code infomation in string:\n" );
	printf( "char : num\n" );
	for( i=0; cStat[i].c != '\0'; i++ )
	{
		if( cStat[i].c == ' ' )
			printf( "空格 : %d\n", cStat[i].num );
		else
			printf( "%3c  : %d\n", cStat[i].c, cStat[i].num );
	}

	//根据待编码字符串中不同字符的信息创建Huffman树
	//Huffman树的结点存储在结构体数组treeNode中
	HuffmanTree *treeNode;
	int nodeNum = 2 * codeNum - 1;
	treeNode = new HuffmanTree[nodeNum];
	HTreeCreation(  cStat, treeNode, codeNum );
	//向屏幕输出Huffman树结点信息,包括 序号、字符、权、双亲序号、左孩子序号、右孩子序号
	printf( "Huffman Tree's node infomation:\n" );
	printf( "%s%s%4c%4c%4c%4c\n", "num ", "Code", 'W', 'P', 'L', 'R'  );
	for( i=0; i<nodeNum; i++ )
	{
		printf( "%3d%5c%4d%4d%4d%4d\n", i, treeNode[i].cChar, treeNode[i].Weight, treeNode[i].Parent, treeNode[i].LChild, treeNode[i].RChild  );
	}
	return 0;
}

/*******************************************************************************
void getStr( CharLinkList &str, int &cTotalNum )
功能:从键盘读入一个待编码的字符串,回车结束输入
输入:str:存储字符串的首地址,单链表
输出:cTotalNum:字符串中全部字符的个数
返回值:无
********************************************************************************/
void getStr( CharLinkList &str, int &cTotalNum )
{
	CharLinkList *node, *p;
	char ch;

	cTotalNum  = 0;
	p = &str;
	//每读入一个字符,即刻存储到一个单链表里面
	while( 1 )//尾插入法创建单链表
	{
		ch = getchar();
		if( ch == '\n' )
			break;
		cTotalNum++;
		node        = new CharLinkList;
		node->cChar = ch;
		node->pNext = NULL;
		p->pNext    = node;
		p           = node;
	}
}//getStr

/*******************************************************************************
void cStatInf( CharLinkList str, int cTotalNum, CharStat *cStat, int &codeNum )
功能:统计每个字符及出现的次数
输入:str:引用,存储字符串的首地址
      cTotalNum:字符串中全部字符的个数
输出:cStat:存储统计结果的数组,每个元素包括2项,分别是字符、次数
      codeNum:不同字符的个数
返回值:无
********************************************************************************/
void cStatInf( CharLinkList str, int cTotalNum, CharStat *cStat, int &codeNum )
{
	int i, j;
	CharLinkList *p;
	int loc;     //记录全新字符存储的位置号
	int newFlag; //新字符标记,如果是第一次出现,则该标志置为1,否则为0

	p = str.pNext;
	for( i=0; i<cTotalNum; i++ )
	{
		cStat[i].c   = '\0';
		cStat[i].num = 0;
	}
	
	loc = 0;
	//从第一个字符开始统计不同的字符出现的次数
	while( p != NULL )
	{
		newFlag = 1;//每读一个字符,都假设是第一次出现
		//获取一个新位置上的字符,如果在此位置之前已经出现了,则计数器自加后结束本轮的查新
		for( j=0; cStat[j].c != '\0'; j++ )
		{
			if( cStat[j].c == p->cChar )
			{
				newFlag = 0;
				cStat[j].num++;
				break;    
			}
		}
		//如果获取的新位置上的字符,在此位置之前没有出现,则存放到第一个空白位置
		if( newFlag == 1 )
		{
			cStat[loc].c = p->cChar;
			cStat[loc].num++;
			loc++;
		}
		p = p->pNext;
	}
	codeNum = loc++;
}//cStatInf

/*******************************************************************************
void HTreeCreation( CharStat *cStat, HuffmanTree *TreeNode, int codeNum )
功能:创建Huffman树
输入:cStat:存储统计结果的数组,每个元素包括2项,分别是字符、次数
      codeNum:不同字符的个数
输出:TreeNode:Huffman树的结点
返回值:无
********************************************************************************/
void HTreeCreation( CharStat *cStat, HuffmanTree *TreeNode, int codeNum )
{
	int i, j, k;
	int w1, w2, p1, p2;
	int nodeNum;
	nodeNum = 2 * codeNum - 1;

	for( i=0; i<nodeNum; i++ )//initial HuffmanTree
	{
		if( i < codeNum )
		{
			TreeNode[i].cChar  = cStat[i].c;
			TreeNode[i].Weight = cStat[i].num;
		}
		else
		{
			TreeNode[i].cChar  = '\0';
			TreeNode[i].Weight = 0;
		}
		TreeNode[i].LChild = BLANKFLAG;
		TreeNode[i].RChild = BLANKFLAG;
		TreeNode[i].Parent = BLANKFLAG;
		
	}
	//找最小的两个权生成一棵子树,存储到第一个非叶子结点,依此,直到只剩下一个结点
	for( j=codeNum; j<nodeNum; j++ )
	{
		w1 = 0xFFFFFFF;
		w2 = w1;
		p1 = p2 = BLANKFLAG;
		for( k=0; k<j; k++ )
		{
			if( TreeNode[k].Weight < w1 && TreeNode[k].Parent == BLANKFLAG )
			{
				w1 = TreeNode[k].Weight;
				p1 = k;
			}
		}
		for( k=0; k<j; k++ )
		{
			if( TreeNode[k].Weight < w2 && k != p1  && TreeNode[k].Parent == BLANKFLAG )
			{
				w2 = TreeNode[k].Weight;
				p2 = k;
			}
		}
		TreeNode[j].Weight  = w1 + w2;
		TreeNode[j].LChild  = p1;
		TreeNode[j].RChild  = p2;
		TreeNode[p1].Parent = j;
		TreeNode[p2].Parent = j;
	}
}//HTreeCreation

4.测试结果
在这里插入图片描述

  • 6
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
霍夫曼编码是一种变长编码,将频率高的字符用较短的编码表示,频率低的字符用较长的编码表示,从而达到压缩数据的效果。在解压数据时,需要对编码进行译码,还原原始数据。以下是利用C语言实现霍夫曼译码的基本步骤: 1. 定义霍夫曼树数据结构。可以使用二叉来实现霍夫曼树,每个节点包含一个字符和对应的编码。 2. 建立霍夫曼树。根据输入的霍夫曼编码表,可以构建出霍夫曼树。 3. 读取编码文件。从编码文件中读取霍夫曼编码字符串。 4. 译码。从根节点开始遍历霍夫曼树,根据读取的编码字符选择左子或右子,直到找到叶子节点,即为对应的字符。将译码出的字符输出到解码文件中。 下面是一个简单的霍夫曼译码的C语言实现示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct Node { char ch; struct Node* left; struct Node* right; } Node; Node* buildHuffmanTree(char** codeTable, char* chars, int* freq, int size) { Node* nodes[size]; for (int i = 0; i < size; ++i) { nodes[i] = (Node*) malloc(sizeof(Node)); nodes[i]->ch = chars[i]; nodes[i]->left = NULL; nodes[i]->right = NULL; } while (size > 1) { int i, j; if (freq[0] < freq[1]) { i = 0; j = 1; } else { i = 1; j = 0; } for (int k = 2; k < size; ++k) { if (freq[k] < freq[j]) { j = k; if (freq[j] < freq[i]) { int temp = i; i = j; j = temp; } } } Node* left = nodes[i]; Node* right = nodes[j]; Node* parent = (Node*) malloc(sizeof(Node)); parent->ch = '\0'; parent->left = left; parent->right = right; freq[j] = freq[i] + freq[j]; freq[i] = freq[size - 1]; nodes[i] = parent; nodes[j] = nodes[size - 1]; size--; } return nodes[0]; } int decodeHuffman(char* code, int length, Node* root, char* output) { int index = 0; Node* current = root; for (int i = 0; i < length; ++i) { if (code[i] == '0') { current = current->left; } else { current = current->right; } if (current->left == NULL && current->right == NULL) { output[index++] = current->ch; current = root; } } output[index] = '\0'; return index; } int main() { char* codeTable[5] = {"10", "11", "00", "010", "011"}; char chars[5] = {'a', 'b', 'c', 'd', 'e'}; int freq[5] = {10, 20, 30, 15, 25}; Node* root = buildHuffmanTree(codeTable, chars, freq, 5); char* code = "100111001011000101010110"; char output[100]; int length = strlen(code); int size = decodeHuffman(code, length, root, output); printf("Decoded string: %s\n", output); return 0; } ``` 这个示例实现了一个简单的霍夫曼译码,输入的编码字符串为"100111001011000101010110",输出的解码字符串为"cbade"。需要注意的是,这个示例只是为了演示霍夫曼译码的基本思路,实际应用中需要根据具体需求进行修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值