使用C语言详解霍夫曼树数据结构

使用C语言详解霍夫曼树数据结构

什么是霍夫曼树

霍夫曼树是一种带权路径长度最短的树,也称为最优二叉树,它是优化编码的核心算法。

在霍夫曼树中,每个叶子节点对应一个字符,该节点的权值为该字符出现的次数。当然,字符可以是任何数据类型。生成霍夫曼树后,在对每个字符进行编码时,字符在霍夫曼树中的路径即为其编码。(一般规定,一条从根到叶子的路径上只出现0或1,从根到某个叶子节点的路径就是该叶子节点对应字符的哈夫曼编码。)

如何生成霍夫曼树

生成霍夫曼树的过程是:首先,把所有的节点按权值从小到大排序,选出权值最小的两个节点组成一棵新的二叉树,它们成为新二叉树的左右子树,新二叉树的根节点的权值是它的左右子树权值之和。在原来的节点序列中,把这两个最小权值的节点删除,并把新生成的根节点加入节点序列。重复上述过程,直到序列中只剩下一个节点为止,这个节点即为霍夫曼树的根节点。

下面是示例代码:

typedef struct _HuffmanTreeNode
{
    int weight;                 // 权重(节点存储的值)
    struct _HuffmanTreeNode *lchild, *rchild;   // 左右子树
}HuffmanTreeNode;

HuffmanTreeNode* CreateHuffmanTree(int weight[], int n)
{
    // 初始化n个叶子结点
    HuffmanTreeNode **nodeList = (HuffmanTreeNode**)malloc(sizeof(HuffmanTreeNode*) * n);
    for (int i = 0; i < n; i++) 
    {
        nodeList[i] = (HuffmanTreeNode*)malloc(sizeof(HuffmanTreeNode));
        nodeList[i]->weight = weight[i];
        nodeList[i]->lchild = nodeList[i]->rchild = NULL;
    }

    // 生成哈夫曼树
    while(n > 1)
    {
        int min1 = 0, min2 = 1;
        if(nodeList[min1]->weight > nodeList[min2]->weight)
        {
            int tmp = min1;
            min1 = min2;
            min2 = tmp;
        }
        for(int i = 2; i < n; i++)
        {
            if(nodeList[i]->weight < nodeList[min1]->weight)
            {
                min2 = min1;
                min1 = i;
            }
            else if(nodeList[i]->weight < nodeList[min2]->weight)
            {
                min2 = i;
            }
        }

        // 组成新的二叉树
        HuffmanTreeNode *newNode = (HuffmanTreeNode*)malloc(sizeof(HuffmanTreeNode));
        newNode->weight = nodeList[min1]->weight + nodeList[min2]->weight;
        newNode->lchild = nodeList[min1];
        newNode->rchild = nodeList[min2];
        nodeList[min1] = newNode;//将两个子节点合并到一个新节点
        nodeList[min2] = nodeList[n-1];//注意将n-1赋值,因为有一个原叶子节点已经被合并
        n--;
    }
    return nodeList[0];
}

如何通过霍夫曼树来编码

霍夫曼编码存在优美的数学性质:任意一个字符的编码都不是另一个字符编码的前缀,所以解码时不会发生歧义。

下面是示例代码:

void GetHuffmanCode(HuffmanTreeNode* root, char* code, char** codes, int depth)
{
    static int count = 0;               // 存储编码字符串的长度
    if(root == NULL)                    // 树到了根节点
        return ;
    if(root->lchild == NULL && root->rchild == NULL)
    {
        code[count] = '\0';
        codes[root->weight] = (char*)malloc(sizeof(char)*count);
        strcpy(codes[root->weight], code);
        return ;
    }
    count++;
    code[count-1] = '0';//左子树编码为0
    GetHuffmanCode(root->lchild, code, codes, depth + 1);
    code[count-1] = '1';//右子树编码为1
    GetHuffmanCode(root->rchild, code, codes, depth + 1);
    count--;
}

示例

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

typedef struct _HuffmanTreeNode
{
    int weight;                 // 权重(节点存储的值)
    struct _HuffmanTreeNode *lchild, *rchild;   // 左右子树
}HuffmanTreeNode;

HuffmanTreeNode* CreateHuffmanTree(int weight[], int n)
{
    // 初始化n个叶子结点
    HuffmanTreeNode **nodeList = (HuffmanTreeNode**)malloc(sizeof(HuffmanTreeNode*) * n);
    for (int i = 0; i < n; i++) 
    {
        nodeList[i] = (HuffmanTreeNode*)malloc(sizeof(HuffmanTreeNode));
        nodeList[i]->weight = weight[i];
        nodeList[i]->lchild = nodeList[i]->rchild = NULL;
    }

    // 生成哈夫曼树
    while(n > 1)
    {
        int min1 = 0, min2 = 1;
        if(nodeList[min1]->weight > nodeList[min2]->weight)
        {
            int tmp = min1;
            min1 = min2;
            min2 = tmp;
        }
        for(int i = 2; i < n; i++)
        {
            if(nodeList[i]->weight < nodeList[min1]->weight)
            {
                min2 = min1;
                min1 = i;
            }
            else if(nodeList[i]->weight < nodeList[min2]->weight)
            {
                min2 = i;
            }
        }
        // 组成新的二叉树
        HuffmanTreeNode *newNode = (HuffmanTreeNode*)malloc(sizeof(HuffmanTreeNode));
        newNode->weight = nodeList[min1]->weight + nodeList[min2]->weight;
        newNode->lchild = nodeList[min1];
        newNode->rchild = nodeList[min2];
        nodeList[min1] = newNode;//将两个子节点合并到一个新节点
        nodeList[min2] = nodeList[n-1];//注意将n-1赋值,因为有一个原叶子节点已经被合并
        n--;
    }
    return nodeList[0];
}

void GetHuffmanCode(HuffmanTreeNode* root, char* code, char** codes, int depth)
{
    static int count = 0;               // 存储编码字符串的长度
    if(root == NULL)                    // 树到了根节点
        return ;
    if(root->lchild == NULL && root->rchild == NULL)
    {
        code[count] = '\0';
        codes[root->weight] = (char*)malloc(sizeof(char)*count);
        strcpy(codes[root->weight], code);
        return ;
    }
    count++;
    code[count-1] = '0';//左子树编码为0
    GetHuffmanCode(root->lchild, code, codes, depth + 1);
    code[count-1] = '1';//右子树编码为1
    GetHuffmanCode(root->rchild, code, codes, depth + 1);
    count--;
}

void PrintCode(char** codes,int len)
{
    for(int i=0;i<len;i++)
    {
        printf("%d %s\n",i,codes[i]);
    }
}

int main()
{
    int weight[] = {5, 6, 8, 7, 15 ,10};
    char* codes[6] = {0};
    char code[100];
    HuffmanTreeNode *root = CreateHuffmanTree(weight, 6);
    GetHuffmanCode(root, code, codes, 0);
    PrintCode(codes,6);
    return 0;
}

以上代码的输出结果为:

0 011
1 010
2 111
3 110
4 00
5 101

其中第i行表示值为i的节点对应的编码为code[i]。

总结

霍夫曼树不仅可以用于编码和压缩,还可以应用于诸如数据存储、加密和网络通信等领域,掌握霍夫曼树对于算法理解和应用编程都有很大的帮助。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:使用C语言详解霍夫曼树数据结构 - Python技术站

(0)
上一篇 2023年5月17日
下一篇 2023年5月17日

相关文章

  • C语言类的双向链表详解

    C语言类的双向链表详解 基本概念 什么是双向链表? 双向链表是链表的一种,它有两个指针域:一个指向前一个结点,一个指向后一个结点。每个结点包含两个部分:数据和指针域,指针域分别指向前一个结点和后一个结点,所以每个结点都是由数据和两个指针域构成的。 双向链表的作用? 双向链表可以支持O(1)时间复杂度的在任何一个结点前或后插入一个结点。 双向链表的实现方式? …

    数据结构 2023年5月17日
    00
  • 稀疏数组

    引入 当在网页上下棋类游戏时,玩到中途想要离开,但是我们需要保存进度,方便下次继续 我们应该怎么实现 ? 以围棋举例 使用二维数组将棋盘记下 ,如 0 为 没有棋子 ,1 为 黑子 , 2为白子 但是没有棋子的地方都为 0 ,整个二维数组充斥着大量的无效数据 0 我们需要想一个办法来 优化存储的方式 基本介绍 当一个数组中大部分元素是同一个值时,我们可以使用…

    算法与数据结构 2023年4月19日
    00
  • 带你了解Java数据结构和算法之前缀,中缀和后缀表达式

    带你了解Java数据结构和算法之前缀、中缀和后缀表达式 1. 前缀表达式(Prefix Expression) 前缀表达式是指运算符位于操作数之前的表达式,也被称为波兰式。前缀表达式的优点在于,每个运算符的优先级都可以通过括号来明确,不需要考虑运算符的优先级。同时,整个表达式的意义也可以很清晰地传达。 举个例子,下面是一个前缀表达式: + * 4 5 6 /…

    数据结构 2023年5月17日
    00
  • 一、对系统理论的认识

           经过一周的时间学习,我们知道了系统的定义:是一个由一组相互连接的要素构成的,能够实现某个目标的整体,任何一个系统都包括三种构成要件:要素连接,功能或目标。       1.系统的连接使得系统呈现特定的结构,使得系统的各个部件连接而产生系统特有的功能—相关性导新功能涌现。连接的媒介—“三流”(信息流,能量流,物质流)。       2.系统的静态…

    算法与数据结构 2023年4月18日
    00
  • Java 中很好用的数据结构(你绝对没用过)

    Java 中很好用的数据结构(你绝对没用过) 介绍 Java 中的数据结构有很多,比如数组、链表、栈、队列、堆、树等等。在这些常见的数据结构中,我们或多或少都会使用到。但是本篇文章要讲述的是一些比较冷门,但是很好用的数据结构。 双向队列(Deque) 双向队列,顾名思义,是一种可以双向操作的队列。它可以从队列的两端插入和删除元素,因此常被用作实现栈和队列以及…

    数据结构 2023年5月17日
    00
  • 考研数据结构模板:顺序表、链表、栈、队列

    考研数据结构模板:顺序表、链表、栈、队列 前言 代码风格偏向于考研风格而非算法竞赛风格。 代码实现参考《2024数据结构王道复习指导》。 注释详细、保证看懂。 下面是已实现的数据结构模板: 顺序表SeqList 链表LinkList 双链表DLinkList 顺序栈SeqStack 循环顺序队列CircleQueue 链队列LinkQueue 顺序表SeqL…

    算法与数据结构 2023年4月17日
    00
  • 1811 E Living Sequence 两种解法

    思维 进制转换 数位DP 无前导0 T3Problem – 1811E – Codeforces 题目大意 从一个不含有数字4的递增序列中找第k个数并输出。如 \(1,2,3,5,6,7,8,9,10,11,12\), \(k = 4\) 时输出 \(5\)。 思路1 有一个巧妙的解法:考虑这个问题, 从一个没有限制的从1开始的递增序列找出第k个数, 显然就…

    算法与数据结构 2023年4月17日
    00
  • golang中set数据结构的使用示例

    Golang中Set数据结构的使用示例 Set是一种无序的、元素不重复的数据结构。通过使用map来实现,map中的key即为Set中的元素,value则可以用来存储某种状态(比如计数)。 Set数据结构的定义 type Set struct { m map[interface{}]bool } Set数据结构的初始化 func NewSet() *Set {…

    数据结构 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部