霍夫曼编码(贪心算法)

1.问题分析

采用贪心算法来实现霍夫曼编码。

2.算法设计思路

    首先输入帯权值节点个数构造霍夫曼树,再利用贪心算法对节点进行编码,在对哈夫曼树编码的过程中,先对权值较大的节点进行编码,在编码的过程中它们的前缀中不能与其他已经编码过的节点相同,这样是为了在解码的过程中更加容易;霍夫曼编码的具体过程为采取可变长编码方式,对文件中出现次数多的字符采取比较短的编码,对于出现次数少的字符采取比较长的编码,可以有效地减小总的编码长度。

例如,在英文中,e的出现频率最高,z的出现频率最低,所以可以用最短的编码来表示e,用最长的编码表示z。

(1)构建霍夫曼树:

算法:输入是没有相同元素的字符数组(长度n)以及字符出现的频率,输出是哈夫曼树。

即假设有n个字符,则构造出得哈夫曼树有n个叶子结点。n个字符的权值(频率)分别设为w1,w2,…,wn,则哈夫曼树的构造规则为:

1)将w1,w2,…,wn看成是有n棵树的森林(每棵树仅有一个结点);

2)在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;

3)从森林中删除选取的两棵树,并将新树加入森林;

4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树;

(2)编码算法:

1)从树根开始在HFTree里查找w,向左走记为0;

2)向右走记为0,最终的01串就是w的编码;

3)算法思想类似于二叉树的非递归遍历算法;

4)利用栈来存放上一个树节点的信息;

3.算法实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
    unsigned int weight;  //用来存放各个结点的权值
    unsigned int parent,LChild,RChild;  //指向双亲、孩子结点的指针

} HTNode, *HuffmanTree;  //动态分配数组,存储哈夫曼树

typedef char *HuffmanCode;  //动态分配数组,存储哈夫曼编码

//选择两个parent为0,且weight最小的结点s1和s2
void Select(HuffmanTree *ht,int n,int *s1,int *s2)
{
    int i,min;
    for(i=1; i<=n; i++)
    {
        if((*ht)[i].parent==0)
        {
            min=i;
            break;
        }
    }
    for(i=1; i<=n; i++)
    {
        if((*ht)[i].parent==0)
        {
            if((*ht)[i].weight<(*ht)[min].weight)
                min=i;
        }
    }
    *s1=min;
    for(i=1; i<=n; i++)
    {
        if((*ht)[i].parent==0 && i!=(*s1))
        {
            min=i;
            break;
        }
    }
    for(i=1; i<=n; i++)
    {
        if((*ht)[i].parent==0 && i!=(*s1))
        {
            if((*ht)[i].weight<(*ht)[min].weight)
                min=i;
        }
    }
    *s2=min;
}

//构造哈夫曼树ht,w存放已知的n个权值
void CrtHuffmanTree(HuffmanTree *ht,int *w,int n)
{
    int m,i,s1,s2;
    m=2*n-1;    //总共的结点数
    *ht=(HuffmanTree)malloc((m+1)*sizeof(HTNode));
    for(i=1; i<=n; i++)  //1--n号存放叶子结点,初始化
    {
        (*ht)[i].weight=w[i];
        (*ht)[i].LChild=0;
        (*ht)[i].parent=0;
        (*ht)[i].RChild=0;
    }
    for(i=n+1; i<=m; i++)   //非叶子结点的初始化
    {
        (*ht)[i].weight=0;
        (*ht)[i].LChild=0;
        (*ht)[i].parent=0;
        (*ht)[i].RChild=0;
    }
    printf("\n哈夫曼树为: \n");
    for(i=n+1; i<=m; i++)   //创建非叶子结点,建哈夫曼树
    {
        //在(*ht)[1]~(*ht)[i-1]的范围内选择两个parent为0且weight最小的结点,其序号分别赋值给s1、s2
        Select(ht,i-1,&s1,&s2);
        (*ht)[s1].parent=i;
        (*ht)[s2].parent=i;
        (*ht)[i].LChild=s1;
        (*ht)[i].RChild=s2;
        (*ht)[i].weight=(*ht)[s1].weight+(*ht)[s2].weight;
        printf("%d (%d, %d)\n",(*ht)[i].weight,(*ht)[s1].weight,(*ht)[s2].weight);
    }
    printf("\n");
}

//从叶子结点到根,逆向求每个叶子结点对应的哈夫曼编码
void CrtHuffmanCode(HuffmanTree *ht, HuffmanCode *hc, int n)
{
    char *cd;   //定义的存放编码的空间
    int a[100];
    int i,start,p,w=0;
    unsigned int c;
    hc=(HuffmanCode *)malloc((n+1)*sizeof(char *));  //分配n个编码的头指针
    cd=(char *)malloc(n*sizeof(char));  //分配求当前编码的工作空间
    cd[n-1]='\0';  //从右向左逐位存放编码,首先存放编码结束符
    for(i=1; i<=n; i++)  //求n个叶子结点对应的哈夫曼编码
    {
        a[i]=0;
        start=n-1;  //起始指针位置在最右边
        for(c=i,p=(*ht)[i].parent; p!=0; c=p,p=(*ht)[p].parent)  //从叶子到根结点求编码
        {
            if( (*ht)[p].LChild==c)
            {
                cd[--start]='1';  //左分支标1
                a[i]++;
            }
            else
            {
                cd[--start]='0';  //右分支标0
                a[i]++;
            }
        }
        hc[i]=(char *)malloc((n-start)*sizeof(char));  //为第i个编码分配空间
        strcpy(hc[i],&cd[start]);    //将cd复制编码到hc
    }
    free(cd);
    for(i=1; i<=n; i++)
        printf(" 权值为%d的哈夫曼编码为:%s\n",(*ht)[i].weight,hc[i]);
    for(i=1; i<=n; i++)
        w+=(*ht)[i].weight*a[i];
    printf(" 带权路径为:%d\n",w);
}

int main()
{
    HuffmanTree HT;
    HuffmanCode HC;
    int *w,i,n,wei;
    printf("**哈夫曼编码**\n" );
    printf("请输入结点个数:" );
    scanf("%d",&n);
    w=(int *)malloc((n+1)*sizeof(int));
    printf("\n输入这%d个元素的权值:\n",n);
    for(i=1; i<=n; i++)
    {
        printf("%d: ",i);
        fflush(stdin);
        scanf("%d",&wei);
        w[i]=wei;
    }
    CrtHuffmanTree(&HT,w,n);
    CrtHuffmanCode(&HT,&HC,n);
}

4.运行结果 

5.算法分析

霍夫曼编码的时间算法复杂度为:O(nlogn);

6.经验归纳与总结

(1)哈夫曼编码的平均码长要比等长编码短。由此操作码得以优化。

(2)哈夫曼二叉树的构造为左1右0。

(3)因程序中权值设置为整型,故若输入权值中有小数,请经所有数乘以10^n倍,将其转换成整型集合。

哈夫曼编码算法:每次将集合中两个权值最小的二叉树合并成一棵新二叉树,n-1次合并后,成为最终的一棵哈夫曼树。这既是贪心法的思想:从某一个最初状态出发,根据当前的局部最优策略,以满足约束方程为条件,以使目标函数最快(或最慢)为原则,在候选集合中进行一系列的选择,以便尽快构成问题的可行解。每次选择两个权值最小的二叉树时,规定了较小的为左子树。

猜你喜欢

转载自blog.csdn.net/qq_40513088/article/details/85339443