贪心算法哈弗曼编码java_霍夫曼编码(贪心算法)

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

#include

#include

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

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

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.运行结果

贪心算法哈弗曼编码java_霍夫曼编码(贪心算法)_第1张图片

5.算法分析

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

6.经验归纳与总结

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

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

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

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

你可能感兴趣的:(贪心算法哈弗曼编码java)