哈夫曼二叉树编码和译码实现总结

测试用例:

7
h1
e1
l3
o2
w1
r1
d1
000001101011101011101110110

期望输出:

h:000
e:001
w:010
r:011
l:10
d:110
o:111
helloworld
节点权值parent左孩子右孩子字符
11800h
21800e
331200l
421000o
51900w
61900r
711000d
821112
921156
1031274
1141389
12613310
131001112

合并节点流程

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

/* 宏定义 */
#define N 30
#define Max (2 * N - 1)
/*
 * 上面定义了默认参数 N 个字符
 * Max 默认节点的个数
 *
 */


/* 创建哈夫曼结构体 */
typedef struct {
    double weight;/* 节点权值 */
    char s; //数据域
    int parent,lchild,rchild; //父亲 左孩子 右孩子
}HTNode,HuffmanTree[Max+1]; //默认哈夫曼树有Max+1个节点
/* HTNode 结构体类型别名 HuffmanTree 结构体数组;
 * HuffmanTree[Max+1] 解析:
 * ∵ 每两个最小权值生成新的结点 0号位不用
 * ∴ 2*N-1;
 * 叶结点没有孩子 也就是 lchild 和 rchild 都 = 0
 * 根节点没有双亲 也就是 parent = 0
 * 左分支标注 0 , 右分支标注 1
 * */

/* 函数定义声明 */
void Select(HuffmanTree ht,int j,int *m1,int *m2);  //找出森林集合中根权值最小的两个
void CreatHuffmanTree(HuffmanTree ht,int n); // 构建哈夫曼树
void HuffmanCoding(HuffmanTree ht, char **hc, int n);//哈夫曼编码
void printHuffmanCode(HuffmanTree ht,char **hc,int index);//先序打印
void HuffmanDecoding(HuffmanTree  ht,int n, char *pwd);//在生成的哈夫曼编码中查询目标

/* 找出森林集合中根权值最小的两个 */
void Select(HuffmanTree ht,int j,int *m1,int *m2){

    /*定义两个变量 用于寻找树中两个最小的叶子节点权值*/
    double min1 = 9999999;
    double min2 = 9999999;
    /* 循环找出第一个最小叶子权值 */
    for(int i = 1; i<= j;i++){
        if(ht[i].weight < min1 && ht[i].parent == 0){
            min1 = ht[i].weight; //最小值替换
            *m1 = i; //存放位置
        }
    }
/* 循环找出第二个最小叶子权值 */
    for(int i = 1; i<= j;i++){
        if(ht[i].weight < min2 && ht[i].parent == 0 && i!=*m1){  // i!=*m1 第二次找到的位置不能是我前面找到的位置
            min2 = ht[i].weight; //最小值替换
            *m2 = i; //存放位置
        }
    }

}

/* 构建哈夫曼树 */
void CreatHuffmanTree(HuffmanTree ht,int n){ //n为用户输入的字符个数 及为叶子结点树
    /* i从n+1开始,创建第n+1个结点 每循环一次就创建一个新的父亲结点 直到森林变成一颗哈夫曼树*/
    for (int i = n+1; i <= 2 * n - 1 ; i++) {
        int m1,m2;
        Select(ht,i-1,&m1,&m2);
        //从n+1开始建
        ht[i].weight = ht[m1].weight + ht[m2].weight; //构建新结点 权值为ht[m1] + ht[m2] 权值的和
        ht[i].lchild = m1; //左孩子 m1 通常来说给左给右都行, 实际中偏向于 左边第一小 右边第二小 左0 右1
        ht[i].rchild = m2; //右孩子 m2
        ht[i].parent = 0;

        ht[m1].parent = i;// ht[m1]的父亲为i
        ht[m2].parent = i;// ht[m2]的父亲为i
    }

}

/* 哈夫曼编码 */
void HuffmanCoding(HuffmanTree ht, char **hc, int n){
    char *cd = (char*) malloc(n * sizeof(char));//cd临时存放哈夫曼编码
    cd[n-1] = '\0';//根节点编码为空  C语言中,字符串以'\0'作为结束标志,告诉编译器在这个字符后面没有更多的字符了

    int now = 1; //此时正在编码的结点
    int p = 0; //正在编码结点的父亲结点
    int start; //此时编码存放在数组中的位置
    int i = 0;

    while(i < n){
    /* 外层循环用于初始化,跟换需要编码的结点 */
    start = n-1;//编码永远在数组中指定位置开始存放
    now = i+1;//随着while循环的推进,编码的起点也在更换
    p = ht[now].parent; // 父亲结点初始化为now结点的父亲位置
       /* 内层循环用于获得指定结点的编码 */
        while(p != 0){
            start--;

            if(ht[p].lchild == now){
                cd[start] = '0';
            } else{
                cd[start] = '1';
            }

            now = p;
            p = ht[now].parent;

        }
        hc[i+1] = (char*) malloc((n-start)* sizeof(char));//开辟存放编码数组内存
        strcpy(hc[i+1],&cd[start]);//传指针复制编码
        i++;
    }
}

/* 先序打印 */
void printHuffmanCode(HuffmanTree ht,char **hc,int index){
    if(index >= 1){

        if(ht[index].lchild == 0 && ht[index].rchild == 0){
            printf("%c:%s\n",ht[index].s,hc[index]);
            return;
        }
        printHuffmanCode(ht,hc,ht[index].lchild);//递归
        printHuffmanCode(ht,hc,ht[index].rchild);
    }
}

/* 在生成的哈夫曼树中查找目标 */
void HuffmanDecoding(HuffmanTree  ht,int n, char *pwd){
    //从根结点出发,是0走左子树,1走又子树,知道遇到叶子结点,然后再从根结点出发
    printf("译码:");
    int len = strlen(pwd);//获取用户输入编码的长度
    int i = 0;
    int node = 2 * n-1;//初始化为从根结点出发
    while(i < len){
        if(pwd[i] == '0'){//是0走左子树
            node = ht[node].lchild;
            i++;
            if(ht[node].lchild == 0 && ht[node].rchild == 0){//如果这是叶子结点,输出此结点的字符
                printf("%c",ht[node].s);

                node = 2*n-1; //重新从根结点出发,继续译码
            }
        }

        if(pwd[i] == '1'){//是1走右子树
            node = ht[node].rchild;
            i++;
            if(ht[node].rchild == 0 && ht[node].rchild == 0){//如果这是叶子结点,输出此结点的字符
                printf("%c",ht[node].s);
                node = 2*n-1; //重新从根结点出发,继续译码
            }
        }

    }


}

int main() {

    HuffmanTree ht;  //一个数组

    int n; //输入字符个数
    printf("多少个字符:");
    scanf("%d",&n);
    getchar();

    char *hc[n+1];  //用于存放字符编码

    /* 二叉树初始化 其实是叶子初始化 */
    for (int i = 1; i <= n ; i++) {//循环获取每一个字符和对应的频率
        scanf("%c%lf",&ht[i].s,&ht[i].weight);//输入字符与频率
        getchar();
        ht[i].lchild = 0;
        ht[i].rchild = 0;
        ht[i].parent = 0;
    }

    char pwd[9999]; //需要译码得到字符串
    scanf("%s",pwd);

    //创建哈夫曼二叉树
    CreatHuffmanTree(ht,n);

    //编码
    HuffmanCoding(ht,hc,n);

    //先序打印
    printHuffmanCode(ht,hc,2*n-1);

    //译码
    HuffmanDecoding(ht,n,pwd);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值