【Registro de aprendizaje de estructura de datos 17】 —— Árbol de Huffman y código de Huff

1. Árbol de Huffman

1. Concepto

En primer lugar, debemos comprender algunos conceptos:

路径: La ruta de un nodo a otro.

路径长度: Se llama al número de ramas de la ruta 路径长度.

带权路径长度: La suma de las longitudes de ruta ponderadas de todos los nodos hoja.

Como se muestra en la figura siguiente, la longitud de la ruta del árbol es: 1+2+3+3=9y la ruta ponderada es:1*7+2*5+3+2+3*4=35

Inserte la descripción de la imagen aquí

2. Darse cuenta

Entonces, ¿cómo construimos un árbol de Huffman? No es difícil concluir que debe ser el nodo con mayor potencia y la longitud de su camino debe ser la más corta. Siga los pasos a continuación:

  1. Según el peso, cada nodo se registra como un árbol, denotado como F 1 · · · F n , sus hijos izquierdo y derecho están todos vacíos, y el valor es su peso.
  2. Elija los dos árboles más pequeños entre los N árboles para formar un árbol nuevo. Los hijos izquierdo y derecho del árbol son los dos nodos, y el valor del árbol es la suma de los pesos de los dos nodos. Y entre los N árboles, elimine estos dos árboles y agregue el nuevo árbol construido.
  3. Repita 2 esta acción hasta que solo quede un árbol. Este árbol es nuestro árbol Huffman.

2. Codificación de Huffman

1. Antecedentes

En nuestra comunicación, casi toda la información se envía en forma de código binario. Supongamos que ABCDestamos codificando, existe 00 01 10 11tal codificación. Pero si para reducir el retardo de transmisión y los recursos, podemos limitar el número de bits del código, y el ABCDcódigo es 0 00 1 01para que se puedan reducir algunos bits. Pero habrá un problema, si lo recibimos 0000, entonces esta cadena de códigos será ambigua, lo que podemos entender como ABA、BAA、AAB、AAAA、BB. Entonces necesitamos uno 任意一个字符的编码都不是另一个字符的编码的前缀, este tipo de codificación se llama 前缀编码.

En este momento, 赫夫曼编码hubo un lío.

2. Principio

Establecemos un árbol de Huffman 左儿子como 0 y 右儿子un árbol de Huffman como 1. Luego, un árbol de Huffman puede completar un código:
Inserte la descripción de la imagen aquí

En este punto, si podemos contar la frecuencia de aparición de cada personaje y usar la frecuencia como el peso del personaje, entonces se puede lograr construyendo el árbol de Huffman de estos personajes 最短前缀编码.

Tres. Implementación del código

Inserte la descripción de la imagen aquí

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

#define         OK          1
#define         ERROR       0

typedef struct Tnode {
    
    
    int weight;
    struct Tnode *front;
    struct Tnode *next;
    struct Tnode *lc;
    struct Tnode *rc;
}Tnode;
// 为了在寻找最小结点的时候
//只用一次遍历就找到最小的两个
//采用双向链表的方式比较好储存

Tnode *TreeInit(void);
int TreeListShow(Tnode *list);
Tnode *TreeMake(Tnode *list);
int HCodeShow(Tnode *root, int deep);

int HFcode[100] = {
    
    0};  // 用于存放赫夫曼编码的数组
char HFstr[100] = {
    
    0};  // 用于存放值与权重对应的hash数组

int main()
{
    
    
    Tnode *list = TreeInit();
    Tnode *hftree;
    //TreeListShow(list);
    hftree = TreeMake(list);
    HCodeShow(hftree, 0);
    return 0;
}

Tnode *TreeInit(void)
{
    
    
    int len, i, temp;
    char tstr;
    Tnode *pre, *post;
    //初始化双向链表头
    Tnode *list = (Tnode*)malloc(sizeof(Tnode));
    list->front = NULL;
    list->weight = -1;  
    //pre为了便于循环赋值
    pre = list;
    printf("please input the number of the weights:");
    scanf("%d", &len);
    printf("please input the char-weights(int) like A1,B2,:\n");
    fflush(stdin);  // 清空缓存区,否则%c会读到换行符
    for (i = 0; i < len; ++i)
    {
    
    
        scanf("%c%d,", &tstr, &temp);
        HFstr[temp] = tstr;     //hasp表,HFstr[权重]=字符
        // 创建后结点
        post = (Tnode*)malloc(sizeof(Tnode));
        // 构造双向链表
        pre->next = post;
        post->next = NULL;
        // 初始化结点
        post->weight = temp;
        post->front = pre;
        post->lc = NULL;
        post->rc = NULL;
        // 更新结点指针,都向后移一位
        pre = post;
        post = pre->next;
    }
    return list;
}

int TreeListShow(Tnode *list)
{
    
    
    Tnode *thisNode = list->next;
    printf("The Weights are:\n");
    // 遍历输出当前双向链表里的权重信息
    while(thisNode != NULL)
    {
    
    
        printf("%d ", thisNode->weight);
        thisNode = thisNode->next;
    }
    printf("\n");
    return OK;
}

Tnode *TreeMake(Tnode *list)
{
    
    
    Tnode *thisNode, *newNode;
    Tnode *mmin, *min;
    Tnode *pre, *pos;

    // 循环结束的条件是,双向链表里只有一个有效结点了,所以两个next应该为NULL就结束
    while(list->next->next != NULL)
    {
    
    
        thisNode = list->next->next;    // 遍历指针,从第二个结点开始
        mmin = list->next;              // 赋初值,最小的结点指针为第一个结点
        min = list->next->next;         // 赋初值,次小的结点指针为第二个结点
        while(thisNode != NULL)         // 单次遍历结束条件
        {
    
    
            if(thisNode->weight < mmin->weight)     // 如果该结点比最小的结点还小
            {
    
    
                min = mmin;                         // 次小的结点为刚才最小的结点
                mmin = thisNode;                    // 最小的结点为该结点
            }
            else if (thisNode->weight < min->weight)   // 如果该结点小于次小结点而大于最小结点
            {
    
    
                min = thisNode;                     // 次小结点为这个
            }
            thisNode = thisNode->next;              // 更新遍历指针
        }
        // 一次循环完后,应该存在最小 次小值
        // 删掉最小结点
        pre = mmin->front;
        pos = mmin->next;
        pre->next = pos;
        // 要考虑最小结点是否是最后一个 否则会越界
        if(pos != NULL) pos->front = pre;
        // 同理删掉次小结点
        pre = min->front;
        pos = min->next;
        pre->next = pos;
        if(pos != NULL) pos->front = pre;
        // 生成新结点
        newNode = (Tnode*)malloc(sizeof(Tnode));
        newNode->lc = mmin;
        newNode->rc = min;
        newNode->weight = mmin->weight + min->weight;
        // 把新结点插入到第一个结点
        pos = list->next;
        list->next = newNode;
        newNode->front = list;
        newNode->next = pos;
        // 也要考虑是否为最后一个结点
        if (pos != NULL) pos->front = newNode;
        // 取消注释下面这句话,就可以看到每次迭代后的效果
        //TreeListShow(list);
    }   
    return list->next;
}

int HCodeShow(Tnode *root, int deep)
{
    
    
    int i;
    if (root->lc != NULL || root->rc != NULL)
    {
    
    
        // 左结点非空,遍历
        if (root->lc != NULL)
        {
    
    
            HFcode[deep] = 0;   // 当前深度的编码为0
            HCodeShow(root->lc, deep+1);
        }
        // 右结点非空,遍历
        if (root->rc != NULL)
        {
    
    
            HFcode[deep] = 1;  // 当前深度的编码为1
            HCodeShow(root->rc, deep+1);
        }
    }
    else
    {
    
    
        // 这里是叶子了,可以输出了
        // 根据hash表输出当前字符
        printf("\n%c is coded as:", HFstr[root->weight]);
        // 根据当前深度,输出编码值
        for (i = 0; i < deep; ++i)
        {
    
    
            printf("%d", HFcode[i]);
        }
    }
}

Supongo que te gusta

Origin blog.csdn.net/u011017694/article/details/110150152
Recomendado
Clasificación