#include<stdio.h>
#include<stdlib.h>
#include<String.h>
#include<stdbool.h>
/*C语言中没有C++中的引用 & 字符参数,
如果函数需要对传入指针进行更改地址或者对已经声明但是尚未申请空间的指针申请空间的时候都需要使用二级指针*/
/*访问符号'.'用于访问结构题成员,非结构体指针;
访问符号'->'用于访问结构体指针*/
/*哈夫曼树的相关知识:
1. 需要找出森林集合中根权重最小的两个
2. 构建哈夫曼树
3.哈夫曼编码
4.遍历打印 先序中序后续都可以 哈夫曼树是WPL最小的二叉树所以也叫做最有二叉树
5. 在生成的哈夫曼树中查询目标
*/
//宏定义
//#define N 5
//#define M (2*N - 1)
typedef struct {
double weight; //结点权重
char s; //输入代表的字符
int parent, lchild, rchild; //父亲结点,左右孩子结点
}HTNode,*HuffmanTree;//*HuffumanTree是一个动态分配数组也可以直接定义为数组 HuffumanTree[MaxSize + 1]
//假设输入的字符个数是 n ,也就是叶子结点是n, 那么HuffmanTree应该是 m = 2*n -1个,所以空间要申请2*n个下标为0的不用
//假设输入的权重是w[n]数组
/*函数声明*/
void SelectTwoMin(HuffmanTree T, int x, int *m1,int *m2);
bool InitHuffmanTree(HuffmanTree* T, int n, double w[], char c[]); //初始化哈夫曼树
void CreateHuffmanTree(HuffmanTree T,int n); //n个结点
void HuffmanCoding(HuffmanTree T,char ***hc,int n);
void SelectTwoMin(HuffmanTree T, int x, int *m1, int *m2) {
if (x < 2)
return;//元素小于2无法找到两个最小的
//min1 min2记录最小权重的根在数组中的位置下标
double min1 = 9999;
double min2 = 9999;
//从1-x个下标中寻找,对应第一到第x个
for (int i = 1; i <= x; ++i) {
//在森林中找
if (T[i].weight < min1 && T[i].parent==0) {
//更新第二小
min2 = min1;
*m2 = *m1;
//更新第一小
min1 = T[i].weight;
*m1 = i;
}
else if (T[i].weight < min2 && T[i].parent == 0) {
min2 = T[i].weight;
*m2 = i;
}
}
//遍历一次就可以得出两个最小不需要遍历两次
}
bool InitHuffmanTree(HuffmanTree *T, int n,double w[],char c[]) {
*T = (HuffmanTree)malloc(sizeof(HTNode) * (2*n -1 + 1)); //申请2n个空间 0下标不用;
//如果分配失败那么初始化失败
if (*T == NULL) return false;
//对前1-n下标位置进行初始化 s初始化为空字符 其他初始化为 0
for (int i = 1; i <= 2*n -1; i++) {
//(*T)[i].parent = 0使用.访问是因为数组元素是HTNode是非指针
(*T)[i].parent = 0;
(*T)[i].lchild = 0;
(*T)[i].rchild = 0;
if (i <= n) {
(*T)[i].weight = w[i];
}
else {
(*T)[i].weight = 0;
}
if (i <= n) {
(*T)[i].s = c[i];
}
else {
(*T)[i].s = '\0';
}
}
return true;
}
void CreateHuffmanTree(HuffmanTree T, int n) {
//传入到SelectTwoMin函数中,参数为指针形式,可以在函数内部修改m1 和 m2
int m1, m2;
for (int i = n + 1; i <= 2*n - 1; ++i) {
//找到两个最小的在森林中找 parent = 0
SelectTwoMin(T, i - 1,&m1 ,&m2);
//找到的m1 m2
T[i].lchild = m1;
T[i].rchild = m2;
T[i].weight = T[m1].weight + T[m2].weight;
T[m1].parent = i;
T[m2].parent = i;
}
T[2 * n - 1].parent = -1; //根节点父亲节点特殊处理
}
//由于我定义了一个二级指针hc 存储字符数组 每个数组存储01组合 所以我要对其进行修改时 函数要使用三级指针!!!
void HuffmanCoding(HuffmanTree T, char*** hc, int n) {
*hc = (char**)malloc(sizeof(char*) * n); // 分配指针数组
if (*hc == NULL) return; // 分配失败
for (int i = 1; i <= n; ++i) {
int current = i;
int length = 0;
char* code = (char*)malloc(sizeof(char) * (n + 1)); // 动态分配编码空间
if (code == NULL) {
// 处理内存分配失败的情况
for (int j = 0; j < i - 1; ++j) {
free((*hc)[j]);
}
free(*hc);
*hc = NULL; // 确保 *hc 被置为 NULL
return;
}
while (T[current].parent != -1) {
if (T[T[current].parent].lchild == current)
code[length++] = '0';
else
code[length++] = '1';
current = T[current].parent;
}
code[length] = '\0'; // 添加字符串结束符
// 反转编码字符串
for (int j = 0; j < length / 2; ++j) {
char temp = code[j];
code[j] = code[length - j - 1];
code[length - j - 1] = temp;
}
(*hc)[i - 1] = (char*)malloc(sizeof(char) * (length + 1));
if ((*hc)[i - 1] == NULL) {
// 处理内存分配失败的情况
free(code);
for (int j = 0; j < i - 1; ++j) {
free((*hc)[j]);
}
free(*hc);
return;
}
strcpy_s((*hc)[i - 1], length + 1, code);
free(code);
}
}
void GetWeight(double *w,int n) {
printf("输入权重:");
for (int i = 1; i <= n; i++) {
scanf_s("%lf", &w[i]);
}
w[0] = 0;
}
void GetChar(char *c,int n) {
printf("输入字符:");
for (int i = 1; i <= n; i++) {
while((c[i] = getchar())=='\n');//如果仅仅使用c[i]=getchar();会读取换行符,所以要处理非换行字符
}
c[0] = '\0';
}
//打印测试 打印前x个信息 注意%c是传入字符 %s是传入字符串
void PrintHuffmanTree(HuffmanTree ht,int x) {
printf("结点权重 字符 左孩子 右孩子 父亲节点\n");
for (int i = 1; i <= x; i++) {
printf("%.2f %c %d %d %d \n", ht[i].weight, ht[i].s, ht[i].lchild, ht[i].rchild, ht[i].parent);
}
}
//打印编码:
void PrintHuffmanCode(char **hc,int n,char c[]) {
printf("Huffman Coding:\n");
for (int i = 0; i < n; ++i) {
if (hc[i] != NULL) {
printf("Character %c: %s\n", c[i + 1], hc[i]);
free(hc[i]); // 释放每个编码的内存
}
}
}
int main() {
HuffmanTree ht; //声明一个哈夫曼树
int n;
printf("输入结点个数:");
scanf_s("%d",&n);
int m = 2*n -1;//所有结点数
//初始权重个数 数组初始化必须在声明的时候进行
double* w = (double*)malloc(sizeof(double)*(n+1));
GetWeight(w,n);
//存储输入的字符
char* c = (char*)malloc(sizeof(char) * (n + 1));
GetChar(c,n);
char **hc;
//初始化哈夫曼树
InitHuffmanTree(&ht,n,w,c);
//构造哈夫曼树
CreateHuffmanTree(ht, n);
//打印查看
PrintHuffmanTree(ht,m);
//哈夫曼编码
HuffmanCoding(ht,&hc,n);
PrintHuffmanCode(hc,n,c);
free(w);
free(c);
free(hc);
free(ht);
return 0;
}
哈夫曼树以及哈夫曼编码
于 2024-08-16 19:56:55 首次发布