空间数据结构--哈夫曼树

本文详细介绍了二叉树的遍历方法(先序、中序和后序),以及如何通过这些遍历方式处理树与表达式,包括后序表达式的逆波兰式计算。此外,文章重点讲解了赫夫曼树的概念、构建哈夫曼树的算法,以及如何利用哈夫曼树进行文本文件的压缩和解压缩操作。
摘要由CSDN通过智能技术生成

6.3 遍历二叉树和线索二叉树

6.3.1 遍历二叉树

二叉树的3个基本单元:根节点、左子树、右子树

若限定先左后右可分为:先序遍历、中序遍历、后序遍历

先序遍历:

void printBT(BinTree* T) {
	if (T == NULL) {
		printf("空\n");
		return;
	}
	else {
		printf("%c ", T->data);
		printBT(T->lchild);
		printBT(T->rchild);
	}
}

中序遍历:

void printBT2(BinTree* T) {
	if (T == NULL) {
		printf("空\n");
		return;
	}
	else {
		printBT2(T->lchild);
		printf("%c ", T->data);
		printBT(T->rchild);
	} 
}

后序遍历:

void printBT2(BinTree* T) {
	if (T == NULL) {
		printf("空\n");
		return;
	}
	else {
		printBT2(T->lchild);
        printBT(T->rchild);
		printf("%c ", T->data);
		
	} 
}

树与表达式:

Q1:已知树->后序表达式->逆波兰式计算

Q2:已知有先序和中序->推算出后序

先序:a b d e c f g h i

中序:  d e b a f c h g i

复原二叉树并求出后序:

6.6 赫夫曼树

赫夫曼(Huffman)树,又称最优树,是一类带权路径长度最短的树,有着最广泛的应用

6.6.1最优二叉树(哈夫曼树)

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

路径长度:路径上的分支数目

树的路径长度:从树根到每一结点的路径长度之和

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

树的带权路径长度:……

最优二叉树/哈夫曼树:带权路径长度最小的二叉树

哈夫曼算法叙述:

(1)根据给定n个权值构成n棵二叉树的集合,F={T1,T2,T2……},每棵二叉树Ti中只有一个带权wi的根节点,其左右子树均空

(2)F中选取两颗根结点的权值最小的树作为左右子树,作为左右子树构造一棵新的二叉树,且重置新的二叉树的根结点的权值为其左、右子树上根结点的权值之和

(3)删除这两棵树,将新树加入

(4)重复

6.6.2哈夫曼编码

基础:创建哈夫曼树

上机要求:根据序列与权重,生成哈夫曼树

init code    by 废阿柴    2024/04/22

#include<stdio.h>
#include<stdlib.h>
#include<memory.h>
typedef struct {
	char ch;
	unsigned weight;
	int parent, lchild, rchild;
}HTNode;
const char* infilename = "D:\\c practice data\\huffman_source.txt";
const unsigned int Min=65535;
//1.创建哈夫曼树
HTNode* createHuffTree(const char* infilename,int *n);
//1.1打印HuffTree
void printHuffTree(HTNode* hTree,int n);
//1.2打印每个字符的huffman编码
void printHuffCode(HTNode* hTree, int i);

//找到最小权重的下标
int findMin(HTNode* Htree, int i);

int main() {
	int n=0;
	HTNode* Htree = createHuffTree(infilename,&n);
	printHuffTree(Htree,n);
	for (int i = 0; i < n; i++) {
		printf("%c %d:",Htree[i].ch,Htree[i].weight);
		printHuffCode(Htree, i);
		printf("\n");
	}
	free(Htree);
	return 0;
}
//1.创建哈夫曼树
HTNode* createHuffTree(const char* infilename, int* n) {
	//1.打开文件
	FILE* fp = fopen(infilename, "r");
	if (fp == NULL) {
		printf("error\n");
		return NULL;
	}
	fscanf(fp, "%d\n", n);
	HTNode* Htree = (HTNode*)malloc((2*(*n) - 1)*sizeof(HTNode));
	memset(Htree, 0, (2 * (*n) - 1) * sizeof(HTNode));
	//2.初始化哈夫曼树
	for (int i = 0; i < (*n);i++) {
		char ch;
		int weight;
		fscanf(fp, "%c %d\n", &ch, &weight);
		Htree[i].ch = ch;
		Htree[i].weight = weight;
		Htree[i].parent = Htree[i].lchild=Htree[i].rchild=-1;
	}
	fclose(fp);
	//3.利用算法重建哈夫曼树
	for (int i = (*n); i < (*n)*2 - 1; i++)//这是因为n后都是空的,不需要找
	{
		int n1 = findMin(Htree, i);
		int n2 = findMin(Htree, i);
		Htree[i].parent = -1;
		Htree[i].lchild = n1;
		Htree[i].rchild = n2;
		Htree[i].weight = Htree[n1].weight + Htree[n2].weight;
		Htree[n1].parent = Htree[n2].parent = i;
	}
	return Htree;
}

//1.1打印HuffTree
void printHuffTree(HTNode* hTree, int n) {
	for (int i = 0; i < 2*n-1; i++) {
		printf("%d:%c %d %d %d %d\n",i, hTree[i].ch, hTree[i].weight, hTree[i].parent, hTree[i].lchild, hTree[i].rchild);
	}
}
//1.2打印每个字符的huffman编码
void printHuffCode(HTNode* hTree, int i) {
	int parent = hTree[i].parent;
	//打印的字符一定是叶子结点,那么parent一定不为-1;
	if (parent == -1) {
		return;
	}//递归一定要在开始时设置返回条件
	printHuffCode(hTree, parent);
	int child=-1;
	if (hTree[parent].lchild == i)//为什么是i?
		child = 0;
	if (hTree[parent].rchild == i)
		child = 1;
	printf("%d", child);
	return ;//-----------
}
//找到最小权重的下标
//需要注意的是,有parent的不参与排序(需要排除)
int findMin(HTNode* Htree, int n) {
	int target=-1;
	unsigned int finMin = Min;
	for (int i = 0; i < n; i++) {
		if ((Htree[i].weight < finMin) && Htree[i].parent ==-1) {
			finMin = Htree[i].weight;
			target = i;
		}
	}//如何找到倒数第二大的?前一个做标记
	Htree[target].parent = 0;//这样找n2时就排除了前一个最小的
	return target;
}
进阶:对文本文件的英文字符利用Huffman算法进行压缩,再重新解压

上机要求:
(1)对指定的英文文本文件字符出现频率进行统计,获得权重表

(2)利用权重表创建哈夫曼树

(3)利用哈夫曼树对文本文件进行编码压缩

(4)对压缩编码进行解压缩

init code    by 废阿柴    2024/04/29

(1)定义哈夫曼树、码表结构以及压缩、解压两个函数

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<memory.h>
const char* srcFilename = "D:\\c practice data\\Huffman\\no_zip.txt";
const char* zipFilename = "D:\\c practice data\\Huffman\\zip.dat";
const char* unzipFilename = "D:\\c practice data\\Huffman\\unzip.txt";
const unsigned int Min=65536;
//定义哈夫曼树
typedef struct {
	char ch;
	unsigned int weight;
	unsigned int len;//编码长度
	char* code;
	int parent, lchild, rchild;
}HTNode;
//定义码表结构
typedef struct {
	bool isValid;
	int len;
	char* code;
}HTCode;

//1.对文件进行压缩、构建哈夫曼树、获取压缩编码、存入文件
bool huffZip(const char* srcFilename, const char* zipFilename);

//创建哈夫曼树
HTNode* createHuffTree(int weightTable[], int n);
//找到权重最小值
int findMin(HTNode* Htree, int n);
//删除哈夫曼树
void deleteHuffTree(HTNode* Htree, int n);

//2.对已压缩文件进行解压,并且保存
bool huffUnzip(const char* zipFilename, const char* unzipFilename);

位操作:方便对后续压缩编码进行按位存储

// 取出index位,若取出的index位为0,则GET_BYTE 值为假,否则为真
#define GET_BYTE(vbyte, index) (((vbyte) & (1 << ((index) ^ 7))) != 0)
// 把index位设置为‘1’
#define SET_BYTE(vbyte, index) ((vbyte) |= (1 << ((index) ^ 7)))
// 把index位设置为‘0’ 
#define CLR_BYTE(vbyte, index) ((vbyte) &= (~(1 << ((index) ^ 7))))

主函数部分

int main(int argc,char*argv[]) 
{
	printf("开始压缩……\n");
	if (huffZip(srcFilename, zipFilename)) {
		printf("压缩成功\n");
	}
	else {
		printf("压缩失败!\n");
		return -1;
	}
	printf("开始解压缩……\n");
	if (huffUnzip(zipFilename, unzipFilename)) {
		printf("解压缩成功\n");
	}
	else {
		printf("解压缩失败!\n");
		return -1;
	}
	return 0;
}

压缩文件函数——创建二叉树+找到权重最小值+删除二叉树,释放存储空间

//创建哈夫曼树
HTNode* createHuffTree(int weightTable[], int n) {
	HTNode* Htree = nullptr;
	Htree = (HTNode*)malloc(sizeof(HTNode) * (2 * n - 1));
	memset(Htree, 0, sizeof(HTNode) * (2 * n - 1));
	int index = 0;
	for (int i = 0; i < 256; i++) {
		if (weightTable[i] > 0) {
			Htree[index].ch = (char)i;
			Htree[index].weight = weightTable[i];
			Htree[index].len = 0;
			Htree[index].code = nullptr;
			Htree[index].parent = Htree[index].lchild = Htree[index].rchild = -1;
			index++;
		}
	}
	//重新构造哈夫曼树
	for (int i = n; i < 2 * n - 1; i++) {
		int m1 = findMin(Htree, i);
		int m2 = findMin(Htree, i);
		Htree[i].weight = Htree[m1].weight + Htree[m2].weight;
		Htree[i].parent = -1;
		Htree[i].lchild = m1;
		Htree[i].rchild = m2;
		Htree[m1].parent = Htree[m2].parent = i;
	}
	return Htree;
}

//找到权重最小值
int findMin(HTNode* Htree, int n) {
	int target = -1;
	unsigned int fMin = Min;
	for (int i = 0; i < n; i++) {
		if (Htree[i].weight < fMin && Htree[i].parent == -1) {
			fMin = Htree[i].weight;
			target = i;
		}
	}
	Htree[target].parent = 0;
	return target;
}
//删除哈夫曼树
void deleteHuffTree(HTNode* Htree, int n) {
	for (int i = 0; i < n; i++) {
		if (Htree[i].code != nullptr) {
			free(Htree[i].code);
		}
	}
	free(Htree);
	return;
}

1.对文件进行压缩、构建哈夫曼树、获取压缩编码、存入文件(huffZip函数)

bool huffZip(const char* srcFilename, const char* zipFilename) {
	//1.1对文件进行读取,统计出现的字符和频率,频率作为权重
	FILE* fp = fopen(srcFilename, "r");
	int n=0;//定义字符种类总数
	int  weightTable[256] ;
	memset(weightTable, 0, sizeof(int) * 255);//清除内存中的一些垃圾
	char ch;
	while (!feof(fp)) {
		ch = fgetc(fp);
		//首先先判断是否是规定的字符
		if (ch > 255 || ch < 0)
			continue;
           
        //若ch第一次出现,种类加1
		if (weightTable[ch] == 0) {
			n += 1;//
		}
		weightTable[ch]++;//每次读入一个,对应table位置+1
	}
	fclose(fp);
	fp=NULL;

	//按照权重构建哈夫曼树
	HTNode* Htree = createHuffTree(weightTable,n);

	//针对创建的Huffman树计算字符编码
	HTCode codeTable[256];
	memset(codeTable, 0, sizeof(HTCode) * 256);
	for (int i = 0; i < n; i++) {
		//从每个叶子结点开始
		HTNode temp = Htree[i];
		while (temp.parent != -1) 
		{
		//限定只有前n个才是叶节点
		 //获取字符长度
			Htree[i].len += 1;
			int parent = temp.parent;
			temp = Htree[parent];
		}

	    //开辟编码长度对应的存储空间
		Htree[i].code = (char*)malloc(sizeof(char) * Htree[i].len);
		memset(Htree[i].code, 0, sizeof(char) * Htree[i].len);

	     //计算编码
		temp = Htree[i];
		int index = 0;
		int current = i;
		while (temp.parent != -1) {
			int parent = temp.parent;
			int child = -1;
			if (Htree[parent].lchild == current) {
				child = 0;
			}
			else if (Htree[parent].rchild == current) {
				child = 1;
			}
			Htree[i].code[Htree[i].len - 1 - index] = child;//这里需要仔细思考
//由于从叶子结点到根结点,因此获得的0-1序列存储要在code申请的内存空间中倒着存
			index++;
			temp = Htree[parent];
			current = parent;
		}

	     //从HTNode的code复制到码表codeTable中
		codeTable[Htree[i].ch].isValid = true;
		codeTable[Htree[i].ch].len = Htree[i].len;
		codeTable[Htree[i].ch].code = (char*)malloc(sizeof(char) * Htree[i].len);
		memcpy(codeTable[Htree[i].ch].code, Htree[i].code, sizeof(char) * Htree[i].len);//copy
	}

	//码表写入二进制压缩文件
	FILE* zipfp = fopen(zipFilename,"wb");//注意是wb

	//先写入字符类型数
	fwrite(&n, sizeof(int), 1, zipfp);

	//再写入末尾无效编码数
	int invalidCount = 0;
	fwrite(&invalidCount, sizeof(int), 1, zipfp);

	//写入字符权重表
	fwrite(weightTable, sizeof(int), 256, zipfp);

	//原始文件编码写入压缩文件
	//重新打开fp文件
	fp = fopen(srcFilename, "r");
	char byte=0;
	int bitindex=0;
	while (!feof(fp)) {
		char ch = fgetc(fp);
		//判断拉丁文
		if ((int)ch < 0 ||(int) ch>=256) {
			continue;
		}
		HTCode hCode = codeTable[ch];

		//对hCode的code进行位操作
		for (int i = 0; i < hCode.len; i++) {
			if (hCode.code[i]==0) {
				CLR_BYTE(byte, bitindex);
			}
			else {
				SET_BYTE(byte, bitindex);
			}
			//判断是否读满一个字节
			if (bitindex == 7) {
				fwrite(&byte, sizeof(char), 1, zipfp);
				bitindex = 0;
			}//之所以需要判断为7是因为,实际上此时已经是满8位了
			else { bitindex++; }
		}
	}

	//读完for循环还需要考虑,是不是最终除以8有余数
	if (bitindex != 0)
		fwrite(&byte, sizeof(char), 1, zipfp);//剩余的0-1码仍然以char字节大小读入,但是标记
	invalidCount = 8 - (bitindex+1);//为什么-1-------因为bitindex是从0开始的,而位是从1开始的
	
    //存储一下invalidcount(这里我还没搞懂)
	fseek(zipfp, sizeof(int), 0);
	fwrite(&invalidCount, sizeof(int), 1, zipfp);
	fclose(fp);
	fclose(zipfp);

	//释放内存空间
	for (int i = 0; i < 256; i++) {
		if(codeTable[i].code!=nullptr)
		    free(codeTable[i].code);
	}

	deleteHuffTree(Htree, n);
	return true;
}

2.对已压缩文件进行解压,并且保存(huffUnzip函数)

bool huffUnzip(const char* zipFilename, const char* unzipFilename) {
	FILE* zipfp = fopen(zipFilename, "rb");
	int n = 0;

	//读入字符种类总数
	fread(&n, sizeof(int),1, zipfp);

	//读入末尾无效位数
	int invalidCount = 0;
	fread(&invalidCount, sizeof(int), 1, zipfp);

	//读入权重表
	int weightTable2[256];
	fread(weightTable2, sizeof(int), 256, zipfp);
	
	//根据权重表创建新的哈夫曼树
	HTNode* Htree2 = createHuffTree(weightTable2, n);

	//开始对字符解码,存入目标文件unzipfp
	FILE* unzipfp = fopen(unzipFilename, "w");

	//找到树根位置
	int current = 2 * n - 1 - 1;//初始位置为HuffTree树的树根
	while (!feof(zipfp)) {//按照char字节读zipfp
		char byte;
		fread(&byte, sizeof(char), 1, zipfp);

		int len = 8;
		if (feof(unzipfp)) {//一直不太懂这里什么意思
			//到了文件末尾需要考虑无效位
			len = len - invalidCount;//----------------------------------
		}

        //
		for (int i = 0; i < len; i++) {
			if (GET_BYTE(byte, i) == 0) {
				current = Htree2[current].lchild;
			}
			else {
				current = Htree2[current].rchild;
			}
			if (Htree2[current].lchild == -1 && Htree2[current].rchild == -1) {
				//到达叶节点
				fprintf(unzipfp, "%c", Htree2[current].ch);
				current = 2 * n - 1 - 1;
			}
		}
	}
	fclose(zipfp);
	fclose(unzipfp);
	deleteHuffTree(Htree2, n);

	return true;
}

有潜藏Bug 待修改……

  • 12
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值