数据结构哈夫曼树的基本操作(C语言)

本文详细介绍了哈夫曼树的构建过程,包括从叶子节点的权值开始,通过不断合并最小权值的节点来生成哈夫曼树。同时,探讨了根到叶子节点的路径、带权路径长度的概念,并提供了计算树的带权路径的算法。此外,还阐述了哈夫曼编码的生成方法,规定向左编码为0,向右编码为1,用于数据压缩。最后,给出了完整的C语言实现代码示例。
摘要由CSDN通过智能技术生成

目录

//1、树的结构定义

//2、创建哈夫曼树

//3、 根到叶子的路径

 //4、哈夫曼编码

//完整测试源码


//1、树的结构定义

#define n 4	//叶子结点个数
#define m 2*n-1 //树的个数

typedef struct HFTree {
	int weight;	//权值
	int parent;	//双亲
	int left;	//左儿子
	int right;	//右儿子
}*HF;
HF hftree[m+1];	//下标从1开始计数,树的结点都是存在这个数组里的,通过下标来访问

//2、创建哈夫曼树

先将n个带权值的结点看作n个树(只有一个结点的树),再将两个权值最小的树合并在一起形成一棵新树,新树的根节点权值就为左右子树的权值之和,然后让新树继续和没有合并过的树进行合并,直到合并完只剩一棵树,这棵树就是哈夫曼树。

//创建哈夫曼树
void createHftree() {
	//先初始化数组里的所有结点,因为最后合并了树必然会有m个结点
	for (int i = 1; i <= m; i++) {
		hftree[i] = (HF)malloc(sizeof(struct HFTree));
		hftree[i]->weight = 0;
		hftree[i]->left = 0;
		hftree[i]->parent = 0;
		hftree[i]->right = 0;
	}
	//给每个结点赋值权值
    //这里只赋值到下标为n的结点,因为最开始只有n个结点
	for (int i = 1; i <= n; i++) {
		scanf("%d", &hftree[i]->weight);
	}
	//合并树
	for (int i = n + 1; i <= m; i++) {
		//定义两个变量作两个最小权值的结点的下标
		int p1 = 0;
		int p2 = 0;
		//定义两个变量作两个最小的权值
		int s1 = 32767;//32767是16位计算机下最大的int值
		int s2 = 32767;
		//找出最小的两个值
		for (int j = 1; j <= i-1; j++) {
			if (hftree[j]->parent == 0) {//没有双亲结点就表示他还没有被合并
				if (hftree[j]->weight < s1) {
					s2 = s1;
					s1 = hftree[j]->weight;
					p2 = p1;
					p1 = j;
				}
				else if(hftree[j]->weight<s2){
					s2 = hftree[j]->weight;
					p2 = j;
				}
			}
   		}
		//合并操作
		hftree[p1]->parent = i;
		hftree[p2]->parent = i;
		hftree[i]->left = p1;
		hftree[i]->right = p2;
		hftree[i]->weight = hftree[p1]->weight + hftree[p2]->weight;
	}
}

//3、 根到叶子的路径

1、路径和路径长度:一棵树中,任意两个结点之间的通路称为路径;通路中分支的数量称为路径长度。

2、权及带权路径长度:权是人为假定的赋值给结点的一个值;从根结点到该结点的  路径长度*权值  就是带权路径长度。

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

//根到叶子的路径
//用一个辅助栈
typedef struct PathNode {
	HF stack[m];	//辅助栈
	int top;	//栈顶指针
	int len;	//每一条路径的路径长度
}*PathNode;
PathNode s;
int num = 0;//作树的带权路径
//初始化栈
void init() {
	s->top = -1;
	s->len = -1;
}
//压栈
void Push(HF root) {
	s->stack[++s->top] = root;
	s->len++;
}
//出栈
void Pop() {
	s->top--;
	s->len--;
}
//打印栈
void PrintStack() {
	printf("%d", s->stack[0]->weight);
	int i = 1;
	for (; i <= s->top; ++i) {
		printf("-->%d", s->stack[i]->weight);
	}
	printf(",路径长度为:%d,", s->len);
	printf("带权路径长度为:%d\n", (s->len)*(s->stack[--i]->weight));
	num += (s->len) * (s->stack[i]->weight);//累加树的带权路径
}
//树的路径
void Path(HF root) {
	if (root == NULL)
		return;
	//先把结点压栈
	Push(root);
	//如果是叶子结点就打印栈里的结点
	if (root->left == 0 && root->right == 0)
		PrintStack(); 
	//如果不是,就递归子树
	else {
		Path(hftree[root->left]);
		Path(hftree[root->right]);
	}
	//出栈,改变路径的终点
	Pop();
}

 //4、哈夫曼编码

规定:往左编码为 0 ,往右编码为 1 。

在得到一个结点的编码时,用一个栈暂存编码,结点有左儿子就会递归它的左儿子,所以在此前会往栈里存一个0,如果左子树递归完没有找到目标结点并且栈里有元素的话,那就要退栈,然后回到原来结点递归右儿子,继续上述操作,直到找到了目标结点。

//哈夫曼编码
//用一个辅助栈,用栈来存储编码,向左走就存0,向右就存1
typedef struct Stack {
	int stack[n];	//辅助栈
	int top;	//栈顶指针
}*Stack;
Stack S;
int ok = 0;	//判定变量,为0表示没有找到目标结点
//初始化栈
void init1() {
	S = (Stack)malloc(sizeof(struct Stack));
	S->top = 0;
	ok = 0;	//每一次初始化就顺便给这个变量也初始化了,方便第二次用
}
//哈夫曼编码
void HfCode(HF root,int weight) {    //weight是要编码的结点的权值
	//当结点为空或者结点的权值是目标权值的时候返回
	if (root == NULL || root->weight == weight) {	
		if (root->weight == weight)//如果找到了目标权值的结点
			ok = 1;	//把ok变为1,代表已经找到了要编码的结点
		return;
	}
	//如果没有找到目标结点并且当前结点有左子树
	if (root->left&&ok==0) {
		S->stack[++S->top] = 0;	//因为是向左,所以存0
		HfCode(hftree[root->left], weight);	//然后递归左子树
		if (root->right&&ok==0) {	//如果没有找到目标结点并且当前结点有右子树
			S->stack[++S->top] = 1;	//向右,存1
			HfCode(hftree[root->right], weight);	//递归右子树
		}
	}
	//如果没有找到目标结点并且栈顶指针不为0,就要出栈
	if (ok == 0&&S->top>0)
		S->top--;
}
//打印编码
void PrintCode() {
	for (int i = 1; i <= S->top; i++) {
		printf("%d",  S->stack[i]);
	}
	printf("\n");
}

//完整测试源码

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<malloc.h>

#define n 4	//叶子结点个数
#define m 2*n-1 //树的个数

typedef struct HFTree {
	int weight;	//权值
	int parent;	//双亲
	int left;	//左儿子
	int right;	//右儿子
}*HF;
HF hftree[m+1];	//下标从1开始计数
//创建哈夫曼树
void createHftree() {
	//初始化n个叶子结点
	for (int i = 1; i <= m; i++) {
		hftree[i] = (HF)malloc(sizeof(struct HFTree));
		hftree[i]->weight = 0;
		hftree[i]->left = 0;
		hftree[i]->parent = 0;
		hftree[i]->right = 0;
	}
	//给每个结点赋值权值
	for (int i = 1; i <= n; i++) {
		scanf("%d", &hftree[i]->weight);
	}
	//合并树
	for (int i = n + 1; i <= m; i++) {
		//定义两个变量作两个最小权值的结点的下标
		int p1 = 0;
		int p2 = 0;
		//定义两个变量作两个最小的权值
		int s1 = 32767;
		int s2 = 32767;
		//找出最小的两个值
		for (int j = 1; j <= i-1; j++) {
			if (hftree[j]->parent == 0) {
				if (hftree[j]->weight < s1) {
					s2 = s1;
					s1 = hftree[j]->weight;
					p2 = p1;
					p1 = j;
				}
				else if(hftree[j]->weight<s2){
					s2 = hftree[j]->weight;
					p2 = j;
				}
			}
   		}
		//合并操作
		hftree[p1]->parent = i;
		hftree[p2]->parent = i;
		hftree[i]->left = p1;
		hftree[i]->right = p2;
		hftree[i]->weight = hftree[p1]->weight + hftree[p2]->weight;
	}
}
//菜单
void menu() {
	printf("------------------\n");
	printf("----*哈夫曼树*----\n");
	printf("1、根到叶子的路径\n");
	printf("2、  哈夫曼编码  \n");
	printf("------------------\n");
}
//根到叶子的路径
//用一个辅助栈
typedef struct PathNode {
	HF stack[m];	//辅助栈
	int top;	//栈顶指针
	int len;	//每一条路径的路径长度
}*PathNode;
PathNode s;
int num = 0;//树的带权路径
//初始化栈
void init() {
	s->top = -1;
	s->len = -1;
}
//压栈
void Push(HF root) {
	s->stack[++s->top] = root;
	s->len++;
}
//出栈
void Pop() {
	s->top--;
	s->len--;
}
//打印栈
void PrintStack() {
	printf("%d", s->stack[0]->weight);
	int i = 1;
	for (; i <= s->top; ++i) {
		printf("-->%d", s->stack[i]->weight);
	}
	printf(",路径长度为:%d,", s->len);
	printf("带权路径长度为:%d\n", (s->len)*(s->stack[--i]->weight));
	num += (s->len) * (s->stack[i]->weight);//累加树的带权路径
}
//树的路径
void Path(HF root) {
	if (root == NULL)
		return;
	//先把结点压栈
	Push(root);
	//如果是叶子结点就打印栈里的结点
	if (root->left == 0 && root->right == 0)
		PrintStack(); 
	//如果不是,就递归子树
	else {
		Path(hftree[root->left]);
		Path(hftree[root->right]);
	}
	//出栈,改变路径的终点
	Pop();
}
//哈夫曼编码
//用一个辅助栈,用栈来存储编码,向左走就存0,向右就存1
typedef struct Stack {
	int stack[n];	//辅助栈
	int top;	//栈顶指针
}*Stack;
Stack S;
int ok = 0;	//判定变量
//初始化栈
void init1() {
	S = (Stack)malloc(sizeof(struct Stack));
	S->top = 0;
	ok = 0;	//每一次初始化就顺便给这个变量也初始化了,方便第二次用
}
//哈夫曼编码
void HfCode(HF root,int weight) {
	//当结点为空或者结点的权值是目标权值的时候返回
	if (root == NULL || root->weight == weight) {	
		if (root->weight == weight)//如果找到了目标权值的结点
			ok = 1;	//把ok变为1,代表已经找到了要编码的结点
		return;
	}
	//如果没有找到目标结点并且当前结点有左子树
	if (root->left&&ok==0) {
		S->stack[++S->top] = 0;	//因为是向左,所以存0
		HfCode(hftree[root->left], weight);	//然后递归左子树
		if (root->right&&ok==0) {	//如果没有找到目标结点并且当前结点有右子树
			S->stack[++S->top] = 1;	//向右,存1
			HfCode(hftree[root->right], weight);	//递归右子树
		}
	}
	//如果没有找到目标结点并且栈顶指针不为0,就要出栈
	if (ok == 0&&S->top>0)
		S->top--;
}
//打印编码
void PrintCode() {
	for (int i = 1; i <= S->top; i++) {
		printf("%d",  S->stack[i]);
	}
	printf("\n");
}
//主函数
int main() {
	int elem = 0;	//作要编码的结点的权值
	int chose = 0;
	printf("请输入%d个整数:\n", n);
	createHftree();	//创建哈夫曼
	while (1) {
		menu();
		scanf("%d", &chose);
		switch (chose) {
		case 1:
			s = (PathNode)malloc(sizeof(struct PathNode));
			init();	//初始化辅助栈
			Path(hftree[m]);	//路径
			printf("树的带权路径是:%d\n", num);
			num = 0;	//重置树的带权路径
			break;
		case 2:
			init1();	//初始化辅助栈
			printf("输入要编码的结点的权值:");
			scanf("%d", &elem);
			HfCode(hftree[m],elem);	//编码
			printf("权值为%d的结点的编码是:", elem);
			PrintCode();	//打印
			break;
		default:return;
		}
	}
	return 0;
}

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

多低调

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值