zoukankan      html  css  js  c++  java
  • 数据结构-哈夫曼树

    哈夫曼树的定义

    设二叉树具有n个带权值的叶子节点,那么从根节点到各个叶子节点的路径长度与相应节点权值的乘积的和,叫做二叉树的带权路径长度。

    image

    其中n表示叶子节点的数目,wi和li分别表示叶子节点ki的权值和根到ki之间的路径长度(即从叶子节点到达根节点的分支数)。
    具有最小带权路径长度的二叉树称为哈夫曼树。

    构造哈夫曼树

    根据哈夫曼树的定义,一棵二叉树要使其WPL值最小,必须使权值越大的叶子节点越靠近根节点,而权值越小的叶子节点越远离根节点。

    如何构造一棵哈夫曼树?

    (1)给定的n个权值{W1,W2,...,Wn}构造n棵只有一个叶子节点的二叉树,从而得到一个二叉树的集合F={T1,T2,...,Tn};

    (2)在F中选取根节点的权值最小和次小的两棵二叉树作为左、右子树构造一棵新的二叉树,这棵新的二叉树根节点的权值为其左、右子树根节点权值之和;
    (3)在集合F中删除作为左、右子树的两棵二叉树,并将新建立的二叉树加入到集合F中;
    (4)重复(2)、(3)两步,当F中只剩下一棵二叉树时,这棵二叉树便是所要建立的哈夫曼树。

    哈夫曼树树中的节点类型

    typedef struct
    {
        // 哈夫曼树是顺序存储结构
        char data;    //节点的数据域
        double weight;//权值
        int parent;//双亲结点
        int lchild,rchild;//左节点和右节点
    }HTNode;

    构造哈夫曼树的算法;

    其算法思路是:
         1.n个叶子节点只有data和weight域值,先将所有2n-1个节点的parent、lchild和rchild域置为初值-1。
         2.处理每个非叶子节点ht[i](存放在ht[n]~ht[2n-2]中):从ht[0] ~ht[i-2]中找出根节点(即其parent域为-1)最小的两个节点ht[lnode]和ht[rnode],将它们作为ht[i]的左右子树,ht[lnode]和ht[rnode]的双亲节点置为ht[i],并且ht[i].weight= ht[lnode].weight+ht[rnode].weight。
        3.如此这样直到所有2n-1个非叶子节点处理完毕。
    void CreateHT(HTNode ht[],int n)
    {
        int i,k,lnode,rnode;
        int min1,min2;
        for (i=0;i<2*n-1;i++)            //所有节点的相关域置初值-1
            ht[i].parent=ht[i].lchild=ht[i].rchild=-1;
        for (i=n;i<2*n-1;i++)            //构造哈夫曼树
        {
            min1=min2=32767;                //min1和min2刚开始设为int整型数据最大值
            lnode=rnode=-1;                //lnode和rnode为最小权重的两个节点位置
            for (k=0;k<=i-1;k++)        //for循环完成从0到i-1范围内的去除已处理过的最小的两个节点。
                if (ht[k].parent==-1)    //只在尚未构造二叉树的节点中查找
                {
                    //以下代码完成同时找到最小值lnode和次小值rnode
                    /**
                    用min1表示序列中已知的最小权值(初始时为int最大值,便于比较)min2表示次小于min1的已知权值
                    下面的第一个if找到最小值第二个if找到次小值
                    */
                    if (ht[k].weight<min1)
                    {
                        min2=min1;rnode=lnode;//min2=min1即在保存上一次找到的最小值,min1=ht[k].weigth是将这次找到的最小值存放值min1中
                        min1=ht[k].weight;lnode=k;
                    }
                    else if (ht[k].weight<min2)
                    {
                        min2=ht[k].weight;rnode=k;
                    }
                }
            ht[lnode].parent=i;ht[rnode].parent=i;
            ht[i].weight=ht[lnode].weight+ht[rnode].weight;
            ht[i].lchild=lnode;ht[i].rchild=rnode;
        }
    }

    哈夫曼编码

    具体构造方法

    如下:设需要编码的字符集合为{d1,d2,…,dn},各个字符在电文中出现的次数集合为{w1,w2,…,wn},以d1,d2,…,dn作为叶节点,以w1,w2,…,wn作为各根节点到每个叶节点的权值构造一棵二叉树。
    规定哈夫曼树中的左分支为0,右分支为1,则从根节点到每个叶节点所经过的分支对应的0和1组成的序列便为该节点对应字符的编码。这样的编码称为哈夫曼编码。

    为实现哈夫曼编码算法,设计存放的每个节点的哈夫曼编码的结构类型如下

    typedef struct
    {
        //每一个Hcode节点对应着HTNode的一个节点,由于每个节点的哈弗曼编码长度不一,所以用cd[start]~cd[n]来存储哈夫曼编码
        char cd[N];   //char类型数组用于存放哈夫曼编码即0和1的序列
        int start;//标志着序列的开始,cd[start]~cd[n] 存放的哈夫曼编码
    }HCode;

    求哈夫曼编码的算法

    void CreateHCode(HTNode ht[],HCode hcd[],int n)
    {
        int i,c,f;
        HCode hc;
        for(i=0;i<n;i++)
        {
            hc.start=n;c=i;
            f=ht[i].parent;
            while(f!=-1)
            {
                if(ht[f].lchild==c)
                    hc[hc.start--]='0';
                else
                    hc[hc.start--]='1';
                c=f,f=ht[f].parent;
            }
            hc.start++;
            hcd[i]=hc;
        }
    }

    完整代码:

    //文件名:exp7-6.cpp
    #include <stdio.h>
    #include <string.h>
    #define N 50        //叶子节点数
    #define M 2*N-1        //树中节点总数
    typedef struct
    {
        char data[5];    //节点值
        int weight;        //权重
        int parent;        //双亲节点
        int lchild;        //左孩子节点
        int rchild;        //右孩子节点
    } HTNode;
    typedef struct
    {
        char cd[N];        //存放哈夫曼码
        int start;
    } HCode;
    void CreateHT(HTNode ht[],int n)
    {
        int i,k,lnode,rnode;
        int min1,min2;
        for (i=0;i<2*n-1;i++)            //所有节点的相关域置初值-1
            ht[i].parent=ht[i].lchild=ht[i].rchild=-1;
        for (i=n;i<2*n-1;i++)            //构造哈夫曼树
        {
            min1=min2=32767;                //min1和min2刚开始设为int整型数据最大值
            lnode=rnode=-1;                //lnode和rnode为最小权重的两个节点位置
            for (k=0;k<=i-1;k++)        //for循环完成从0到i-1范围内的去除已处理过的最小的两个节点。
                if (ht[k].parent==-1)    //只在尚未构造二叉树的节点中查找
                {
                    //以下代码完成同时找到最小值lnode和次小值rnode
                    /**
                    用min1表示序列中已知的最小权值(初始时为int最大值,便于比较)min2表示次小于min1的已知权值
                    下面的第一个if找到最小值第二个if找到次小值
                    */
                    if (ht[k].weight<min1)
                    {
                        min2=min1;rnode=lnode;//min2=min1即在保存上一次找到的最小值,min1=ht[k].weigth是将这次找到的最小值存放值min1中
                        min1=ht[k].weight;lnode=k;
                    }
                    else if (ht[k].weight<min2)
                    {
                        min2=ht[k].weight;rnode=k;
                    }
                }
            ht[lnode].parent=i;ht[rnode].parent=i;
            ht[i].weight=ht[lnode].weight+ht[rnode].weight;
            ht[i].lchild=lnode;ht[i].rchild=rnode;
            
        }
    }
    void CreateHCode(HTNode ht[],HCode hcd[],int n)
    {
        int i,f,c;
        HCode hc;
        for (i=0;i<n;i++)    //根据哈夫曼树求哈夫曼编码
        {
            hc.start=n;c=i;
            f=ht[i].parent;
            while (f!=-1)    //循序直到树根节点
            {
                if (ht[f].lchild==c)    //处理左孩子节点
                    hc.cd[hc.start--]='0';
                else                    //处理右孩子节点
                    hc.cd[hc.start--]='1';
                c=f;f=ht[f].parent;
            }
            hc.start++;        //start指向哈夫曼编码最开始字符
            hcd[i]=hc;
        }
    }
    //输出哈夫曼数
    void DispHCode(HTNode ht[],HCode hcd[],int n)
    {
        int i,k;
        int sum=0,m=0,j;
        printf("输出哈夫曼编码:
    "); //输出哈夫曼编码
        for (i=0;i<n;i++)
        {
            j=0;
            printf("      %s:	",ht[i].data);
            for (k=hcd[i].start;k<=n;k++)
            {
                printf("%c",hcd[i].cd[k]);
                j++;
            }
            m+=ht[i].weight;
            sum+=ht[i].weight*j;
            printf("
    ");
        }
        printf("平均长度=%g
    ",1.0*sum/m);
    }
    void main()
    {
        int n=15,i;
        char *str[]={"The","of","a","to","and","in","that","he","is","at","on","for","His","are","be"};
        int fnum[]={1192,677,541,518,462,450,242,195,190,181,174,157,138,124,123};
        HTNode ht[M];
        HCode hcd[N];
        for (i=0;i<n;i++)
        {
            strcpy(ht[i].data,str[i]);
            ht[i].weight=fnum[i];
        }
        CreateHT(ht,n);
        CreateHCode(ht,hcd,n);
        DispHCode(ht,hcd,n);
    }
    作者:leemoaly
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    在博客园里给图片加水印(canvas + drag)
    Chrome开发者工具使用指南
    《古剑奇谭3》千秋戏辅助工具(前端React制作)
    React中useEffect的源码解读
    关于为什么使用React新特性Hook的一些实践与浅见
    使用@babel/preset-typescript取代awesome-typescript-loader和ts-loader
    使用dva改造React旧项目的数据流方案
    在React旧项目中安装并使用TypeScript的实践
    安利一个绘制指引线的JS库leader-line
    微信小程序中悬浮窗功能的实现(主要探讨和解决在原生组件上的拖动)
  • 原文地址:https://www.cnblogs.com/kavs/p/5003330.html
Copyright © 2011-2022 走看看