数据结构之霍夫曼树

1.相关概念
霍夫曼树是所有与树有关的结构中最优美的,也叫做哈夫曼树。在学习霍夫曼树之前必须了解几个概念:

①路径:从树中的一个节点到另一个节点之间的分支结构通路
②路径长度:路径上的分支数目
③树的路径长度:从树根到每一个节点的路径长度之和
④节点的带权路径长度:从该节点开始到树根之间的路径长度与节点上的权重的乘积
⑤树的带权路径长度:是树中所有叶子结点的带权路径长度的和

所谓的霍夫曼树,假设有 n 个权值{a1,a2,…,an},可以构造出一棵具有 n 个叶子节点的二叉树,每个叶子结点的权值为ni,则其中的带权路径长度最短的二叉树称作霍夫曼树,也叫最优二叉树。

2.构造霍夫曼树的过程
①:将 n 个给定的权值作为根节点创建n个树,{T1,T2,…,Tn},即组成了一个森林
②:然后,从上面的森林中选择两棵权值最小的二叉树作为左右子节点合并成一个二叉树,新的二叉树的权值为两个子树的权值之和
③:剔除上面的两个子树,将新生成的二叉树加入森林中
④:重复②和③,直至森林中只有一棵二叉树为止

3.霍夫曼编码
以字符出现的频率为权构造霍夫曼树,在霍夫曼树中从根节点开始往左子树走记为0,往右子树走记为1,知道走到叶子结点,则得到的0,1 符号串即为该字符的霍夫曼编码

④.基本代码


#include 
#include 

typedef struct {
    int weight; //权值 
    int parent; //父结点序号 
    int left; //左子树序号
    int right; //右子树序号 
} HuffmanTree;

typedef char *HuffmanCode;  //Huffman编码 

void SelectNode(HuffmanTree *ht, int n, int *bt1, int *bt2)
//从1~x个结点选择parent结点为0,权重最小的两个结点 
{
    int i;
    HuffmanTree *ht1, *ht2, *t;
    ht1 = ht2 = NULL; //初始化两个结点为空
    for (i = 1; i <= n; ++i) //循环处理1~n个结点(包括叶结点和非叶结点)
    {
        if (!ht[i].parent) //父结点为空(结点的parent=0)
        {
            if (ht1 == NULL) //结点指针1为空
            {
                ht1 = ht + i; //指向第i个结点
                continue; //继续循环
            }
            if (ht2 == NULL) //结点指针2为空
            {
                ht2 = ht + i; //指向第i个结点
                if (ht1->weight > ht2->weight) //比较两个结点的权重,使ht1指向的结点权重小
                {
                    t = ht2;
                    ht2 = ht1;
                    ht1 = t;
                }
                continue; //继续循环
            }
            if (ht1 && ht2) //若ht1、ht2两个指针都有效
            {
                if (ht[i].weight <= ht1->weight) //第i个结点权重小于ht1指向的结点
                {
                    ht2 = ht1; //ht2保存ht1,因为这时ht1指向的结点成为第2小的
                    ht1 = ht + i; //ht1指向第i个结点
                } else if (ht[i].weight < ht2->weight) { //若第i个结点权重小于ht2指向的结点
                    ht2 = ht + i; //ht2指向第i个结点
                }
            }
        }
    }
    if (ht1 > ht2) { //增加比较,使二叉树左侧为叶结点
        *bt2 = ht1 - ht;
        *bt1 = ht2 - ht;
    } else {
        *bt1 = ht1 - ht;
        *bt2 = ht2 - ht;
    }
}

void CreateTree(HuffmanTree *ht, int n, int *w) {
    int i, m = 2 * n - 1;//总的节点数
    int bt1, bt2; //二叉树结点序与
    if (n <= 1) return; //只有一个结点,无法创建
    for (i = 1; i <= n; ++i) //初始化叶结点
    {
        ht[i].weight = w[i - 1];
        ht[i].parent = 0;
        ht[i].left = 0;
        ht[i].right = 0;
    }
    for (; i <= m; ++i)//初始化后续结点
    {
        ht[i].weight = 0;
        ht[i].parent = 0;
        ht[i].left = 0;
        ht[i].right = 0;
    }
    for (i = n + 1; i <= m; ++i) //逐个计算非叶结点,创建Huffman树
    {
        SelectNode(ht, i - 1, &bt1, &bt2); //从1~i-1个结点选择parent结点为0,权重最小的两个结点
        ht[bt1].parent = i;
        ht[bt2].parent = i;
        ht[i].left = bt1;
        ht[i].right = bt2;
        ht[i].weight = ht[bt1].weight + ht[bt2].weight;
    }
}

void HuffmanCoding(HuffmanTree *ht, int n, HuffmanCode *hc) {
    char *cd;
    int start, i;
    int current, parent;
    cd = (char *) malloc(sizeof(char) * n);//用来临时存放一个字符的编码结果
    cd[n - 1] = '\0'; //设置字符串结束标志
    for (i = 1; i <= n; i++) {
        start = n - 1;
        current = i;
        parent = ht[current].parent;//获取当前结点的父结点
        while (parent) //父结点不为空
        {
            if (current == ht[parent].left)//若该结点是父结点的左子树
                cd[--start] = '0'; //编码为0
            else //若结点是父结点的右子树
                cd[--start] = '1'; //编码为1
            current = parent; //设置当前结点指向父结点
            parent = ht[parent].parent; //获取当前结点的父结点序号
        }
        hc[i - 1] = (char *) malloc(sizeof(char) * (n - start));//分配保存编码的内存
        strcpy(hc[i - 1], &cd[start]); //复制生成的编码
    }
    free(cd); //释放编码占用的内存
}

void Encode(HuffmanCode *hc, char *alphabet, char *str, char *code)
//将一个字符串转换为Huffman编码
//hc为Huffman编码表 ,alphabet为对应的字母表,str为需要转换的字符串,code返回转换的结果 
{

    int len = 0, i = 0, j;
    code[0] = '\0';
    while (str[i]) {
        j = 0;
        while (alphabet[j] != str[i])
            j++;
        strcpy(code + len, hc[j]); //将对应字母的Huffman编码复制到code指定位置
        len = len + strlen(hc[j]); //累加字符串长度
        i++;
    }
    code[len] = '\0';
}

void Decode(HuffmanTree *ht, int m, char *code, char *alphabet, char *decode)
//将一个Huffman编码组成的字符串转换为明文字符串 
//ht为Huffman二叉树,m为字符数量,alphabet为对应的字母表,str为需要转换的字符串,decode返回转换的结果 
{
    int position = 0, i, j = 0;
    m = 2 * m - 1;
    while (code[position]) //字符串未结束
    {
        for (i = m; ht[i].left && ht[i].right; position++) //在Huffman树中查找左右子树为空 ,以构造一个Huffman编码
        {
            if (code[position] == '0') //编码位为0
                i = ht[i].left; //处理左子树
            else //编译位为 1
                i = ht[i].right; //处理右子树
        }
        decode[j] = alphabet[i - 1]; //得到一个字母
        j++;//处理下一字符
    }
    decode[j] = '\0'; //字符串结尾
}

测试代码

#include 
#include 
#include 
#include "Huffman.c"
int main()
{
    int i,n=4,m; 
    char test[]="DBDBDABDCDADBDADBDADACDBDBD";
    char code[100],code1[100];
    char alphabet[]={'A','B','C','D'}; //4个字符
    int w[]={5,7,2,13} ;//4个字符的权重 
    HuffmanTree *ht;
    HuffmanCode *hc;    
    m=2*n-1;    
    ht=(HuffmanTree *)malloc((m+1)*sizeof(HuffmanTree)); //申请内存,保存赫夫曼树 
    if(!ht)
    {
        printf("内存分配失败!\n");
        exit(0);    
    }
    hc=(HuffmanCode *)malloc(n*sizeof(char*));
    if(!hc)
    {
        printf("内存分配失败!\n");
        exit(0);    
    }

    CreateTree(ht,n,w); //创建赫夫曼树 
    HuffmanCoding(ht,n,hc); //根据赫夫曼树生成赫夫曼编码 
    for(i=1;i<=n;i++) //循环输出赫夫曼编码 
        printf("字母:%c,权重:%d,编码为 %s\n",alphabet[i-1],ht[i].weight,hc[i-1]);

    Encode(hc,alphabet,test,code); //根据赫夫曼编码生成编码字符串 
    printf("\n字符串:\n%s\n转换后为:\n%s\n",test,code); 

    Decode(ht,n,code,alphabet,code1); //根据编码字符串生成解码后的字符串 
    printf("\n编码:\n%s\n转换后为:\n%s\n",code,code1); 
    getch();
    return 0;
}

你可能感兴趣的:(数据结构)