Huffman编码

要求:
1、编程实现Huffman编码算法程序;
2、要求程序输出显示所有的码字以及编码效率;
3、设计简单的输入界面(可以是简单的文字提示信息),程序运行时提示用户输入要编码的字符串。

代码:

点击查看代码
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

//哈夫曼树结点结构
typedef struct
{
    int weight;              //结点权重
    int parent, left, right; //父结点、左孩子、右孩子在数组中的位置下标
} HTNode, *HuffmanTree;
//动态二维数组,存储哈夫曼编码
typedef char **HuffmanCode;

//HT数组中存放的哈夫曼树,end表示HT数组中存放结点的最终位置,s1和s2传递的是HT数组中权重值最小的两个结点在数组中的位置
void Select(HuffmanTree HT, int end, int *s1, int *s2)
{
    int min1, min2;
    //遍历数组初始下标为 1
    int i = 1;
    //找到还没构建树的结点
    while (HT[i].parent != 0 && i <= end)
    {
        i++;
    }
    min1 = HT[i].weight;
    *s1 = i;

    i++;
    while (HT[i].parent != 0 && i <= end)
    {
        i++;
    }
    //对找到的两个结点比较大小,min2为大的,min1为小的
    if (HT[i].weight < min1)
    {
        min2 = min1;
        *s2 = *s1;
        min1 = HT[i].weight;
        *s1 = i;
    }
    else
    {
        min2 = HT[i].weight;
        *s2 = i;
    }
    //两个结点和后续的所有未构建成树的结点做比较
    for (int j = i + 1; j <= end; j++)
    {
        //如果有父结点,直接跳过,进行下一个
        if (HT[j].parent != 0)
        {
            continue;
        }
        //如果比最小的还小,将min2=min1,min1赋值新的结点的下标
        if (HT[j].weight < min1)
        {
            min2 = min1;
            min1 = HT[j].weight;
            *s2 = *s1;
            *s1 = j;
        }
        //如果介于两者之间,min2赋值为新的结点的位置下标
        else if (HT[j].weight >= min1 && HT[j].weight < min2)
        {
            min2 = HT[j].weight;
            *s2 = j;
        }
    }
}

//HT为地址传递的存储哈夫曼树的数组,w为存储结点权重值的数组,n为结点个数
void CreateHuffmanTree(HuffmanTree *HT, int *w, int n)
{
    if (n <= 1)
        return;                                          // 如果只有一个编码就相当于0
    int m = 2 * n - 1;                                   // 哈夫曼树总节点数,n就是叶子结点
    *HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode)); // 0号位置不用
    HuffmanTree p = *HT;
    // 初始化哈夫曼树中的所有结点
    for (int i = 1; i <= n; i++)
    {
        (p + i)->weight = *(w + i - 1);
        (p + i)->parent = 0;
        (p + i)->left = 0;
        (p + i)->right = 0;
    }
    //从树组的下标 n+1 开始初始化哈夫曼树中除叶子结点外的结点
    for (int i = n + 1; i <= m; i++)
    {
        (p + i)->weight = 0;
        (p + i)->parent = 0;
        (p + i)->left = 0;
        (p + i)->right = 0;
    }
    //构建哈夫曼树
    for (int i = n + 1; i <= m; i++)
    {
        int s1, s2;
        Select(*HT, i - 1, &s1, &s2);
        (*HT)[s1].parent = (*HT)[s2].parent = i;
        (*HT)[i].left = s1;
        (*HT)[i].right = s2;
        (*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight;
    }
}

//HT为哈夫曼树,HC为存储结点哈夫曼编码的二维动态数组,n为结点的个数
void HuffmanCoding(HuffmanTree HT, HuffmanCode *HC, int n)
{
    *HC = (HuffmanCode)malloc((n + 1) * sizeof(char *));
    char *cd = (char *)malloc(n * sizeof(char)); //存放结点哈夫曼编码的字符串数组
    cd[n - 1] = '\0';                            //字符串结束符

    for (int i = 1; i <= n; i++)
    {
        //从叶子结点出发,得到的哈夫曼编码是逆序的,需要在字符串数组中逆序存放
        int start = n - 1;
        //当前结点在数组中的位置
        int c = i;
        //当前结点的父结点在数组中的位置
        int j = HT[i].parent;
        // 一直寻找到根结点
        while (j != 0)
        {
            // 如果该结点是父结点的左孩子则对应路径编码为0,否则为右孩子编码为1
            if (HT[j].left == c)
                cd[--start] = '0';
            else
                cd[--start] = '1';
            //以父结点为孩子结点,继续朝树根的方向遍历
            c = j;
            j = HT[j].parent;
        }
        //跳出循环后,cd数组中从下标 start 开始,存放的就是该结点的哈夫曼编码
        (*HC)[i] = (char *)malloc((n - start) * sizeof(char));
        strcpy((*HC)[i], &cd[start]);
    }
    //使用malloc申请的cd动态数组需要手动释放
    free(cd);
}

//打印哈夫曼编码的函数
void PrintHuffmanCode(HuffmanCode htable, int *array, int n, int *str, int s, double h, double l, double *p)
{
    int i, j;
    double e; //编码效率
    //每个字符的霍夫曼编码
    printf("字符霍夫曼编码:\n");
    for (i = 1; i <= n; i++)
    {
        printf("%c:%s\n", array[i - 1], htable[i]);
    }

    //字符串的霍夫曼编码结果
    printf("霍夫曼编码结果:\n");
    for (i = 0; i <= s; i++)
    {
        for (j = 1; j <= n; j++)
        {
            if (str[i] == array[j - 1])
            {
                printf("%s", htable[j]);
            }
        }
    }

    printf("\n");

    //平均码长
    for (i = 1; i <= n; i++)
    {
        l += (p[i - 1] * (double)strlen(htable[i]));
    }

    //编码效率
    e = 100 * h / l; //转为百分数表示
    printf("编码效率:\n%f%\n", e);
}

int main(void)
{
    int i = 0, j = 0, t = 0;
    int s = 0;          //字符串长度
    int n = 0;          //节点个数
    int num[127] = {0}; //声明一个数组用于存放每个字符出现的个数,比字符ASCII码范围稍大即可
    double h = 0;       //信源的熵
    double l = 0;       //平均码长

    printf("请输入字符串长度:\n");
    scanf("%d", &s);

    printf("请输入字符串\n");
    getchar();
    int str[s] = {0}; //字符串数组
    while (i < s)
    {
        str[i] = getchar();
        i++;
    }

    for (i = 0; i < s; i++)
    {
        for (j = 32; j <= 122; j++)
        {
            //ASCII码32-122位,包括数字、字母大小写、空格等符号
            if (str[i] == j)
            {
                num[j]++;
            }
        }
    }

    //统计每个字符的权重
    for (j = 32; j <= 122; j++) //循环统计霍夫曼树结点个数
    {
        if (num[j] > 0)
        {
            n++; //霍夫曼树结点个数
        }
    }

    //权重统计
    int w[n] = {0};     //权重数组
    int array[n] = {0}; //字符数组
    i = 0;
    printf("每个字符的权重:\n");
    for (j = 32; j <= 122; j++) //循环统计霍夫曼树结点个数
    {
        //输出统计信息
        if (num[j] > 0)
        {
            w[i] = num[j];
            array[i] = j;
            printf("%c:%-3d\n", j, w[i]);
            i++;
        }
    }

    //概率计算
    double p[n] = {0}; //字符概率数组
    for (i = 0; i < n; i++)
    {
        p[i] = double(w[i]) / double(s);
        //printf("p[%d]:%f\n", i, p[i]);
    }

    //信源的熵
    for (i = 0; i < n; i++)
    {
        h += (-p[i] * log2(p[i]));
    }

    // int w[5] = {2, 8, 7, 6, 5};
    // int n = 5;
    HuffmanTree htree;
    HuffmanCode htable;
    CreateHuffmanTree(&htree, w, n);
    HuffmanCoding(htree, &htable, n);
    PrintHuffmanCode(htable, array, n, str, s, h, l, p);
    return 0;
}
上一篇:哈夫曼编码


下一篇:Redis 详解 (自用)