0023算法笔记——【贪心算法】哈夫曼编码问题

合集下载

贪心算法构造哈夫曼树

贪心算法构造哈夫曼树

软件02 1311611006 张松彬利用贪心算法构造哈夫曼树及输出对应的哈夫曼编码问题简述:两路合并最佳模式的贪心算法主要思想如下:(1)设w={w0,w1,......wn-1}是一组权值,以每个权值作为根结点值,构造n棵只有根的二叉树(2)选择两根结点权值最小的树,作为左右子树构造一棵新二叉树,新树根的权值是两棵子树根权值之和(3)重复(2),直到合并成一颗二叉树为一、实验目的(1)了解贪心算法和哈夫曼树的定义(2)掌握贪心法的设计思想并能熟练运用(3)设计贪心算法求解哈夫曼树(4)设计测试数据,写出程序文档二、实验内容(1)设计二叉树结点数据结构,编程实现对用户输入的一组权值构造哈夫曼树(2)设计函数,先序遍历输出哈夫曼树各结点3)设计函数,按树形输出哈夫曼树代码:#include <stdio.h>#include <string.h>#include <time.h>#include <stdlib.h>typedef struct Node{ //定义树结构int data;struct Node *leftchild;struct Node *rightchild;}Tree;typedef struct Data{ //定义字符及其对应的频率的结构int data;//字符对应的频率是随机产生的char c;};void Initiate(Tree **root);//初始化节点函数int getMin(struct Data a[],int n);//得到a中数值(频率)最小的数void toLength(char s[],int k);//设置有k个空格的串svoid set(struct Data a[],struct Data b[]);//初始化a,且将a备份至bchar getC(int x,struct Data a[]);//得到a中频率为x对应的字符void prin(struct Data a[]);//输出初始化后的字符及对应的频率int n;void main(){//srand((unsigned)time(NULL));Tree *root=NULL,*left=NULL,*right=NULL,*p=NULL; int min,num;int k=30,j,m;struct Data a[100];struct Data b[100];int i;char s[100]={'\0'},s1[100]={'\0'};char c;set(a,b);prin(a);Initiate(&root);Initiate(&left);Initiate(&right);Initiate(&p);//设置最底层的左节点min=getMin(a,n);left->data=min;left->leftchild=NULL;left->rightchild=NULL;//设置最底层的右节点min=getMin(a,n-1);right->data=min;right->leftchild=NULL;right->rightchild=NULL;root->data=left->data+right->data;Initiate(&root->leftchild);Initiate(&root->rightchild);//将设置好的左右节点插入到root中root->leftchild=left;root->rightchild=right;for(i=0;i<n-2;i++){min=getMin(a,n-2-i);Initiate(&left);Initiate(&right);if(min<root->data)//权值小的作为左节点{left->data=min;left->leftchild=NULL;left->rightchild=NULL;p->data=min+root->data;Initiate(&p->leftchild);Initiate(&p->rightchild);p->leftchild=left;p->rightchild=root;root=p;}else{right->data=min;right->leftchild=NULL;right->rightchild=NULL;p->data=min+root->data;Initiate(&p->leftchild);Initiate(&p->rightchild);p->leftchild=root;p->rightchild=right;root=p;}Initiate(&p);}num=n-1;p=root;printf("哈夫曼树如下图:\n");while(num){if(num==n-1){for(j=0;j<k-3;j++)printf(" ");printf("%d\n",root->data);}for(j=0;j<k-4;j++)printf(" ");printf("/ \\\n");for(j=0;j<k-5;j++)printf(" ");printf("%d",root->leftchild->data);printf(" %d\n",root->rightchild->data);if(root->leftchild->leftchild!=NULL){root=root->leftchild;k=k-2;}else{root=root->rightchild;k=k+3;}num--;}num=n-1;Initiate(&root);root=p;printf("各字符对应的编码如下:\n");while(num){if(root->leftchild->leftchild==NULL){strcpy(s1,s);m=root->leftchild->data;c=getC(m,b);printf("%c 【%d】:%s\n",c,m,strcat(s1,"0"));}if(root->rightchild->leftchild==NULL){strcpy(s1,s);m=root->rightchild->data;c=getC(m,b);printf("%c 【%d】:%s\n",c,m,strcat(s1,"1"));}if(root->leftchild->leftchild!=NULL){strcat(s,"0");root=root->leftchild;}if(root->rightchild->leftchild!=NULL){strcat(s,"1");root=root->rightchild;}num--;}}int getMin(struct Data a[],int n){int i,t;for(i=1;i<n;i++){if(a[i].data<a[0].data){t=a[i].data;a[i].data=a[0].data;a[0].data=t;}}t=a[0].data;for(i=0;i<n-1;i++){a[i]=a[i+1];}return t;}void toLength(char s[],int k){int i=0;for(;i<k;i++)strcat(s," ");}void Initiate(Tree **root){*root=(Tree *)malloc(sizeof(Tree));(*root)->leftchild=NULL;(*root)->rightchild=NULL;}void set(struct Data a[],struct Data b[]) {int i;srand((unsigned)time(NULL));n=rand()%10+2;for(i=0;i<n;i++){a[i].data=rand()%100+1;a[i].c=i+97;b[i].data=a[i].data;b[i].c=a[i].c;if(i>=0&&a[i].data==a[i-1].data)i--;}}char getC(int x,struct Data b[]){int i;for(i=0;i<n;i++){if(b[i].data==x){break;}}return b[i].c;}void prin(struct Data a[]){int i;printf("字符\t出现的频率\n");for(i=0;i<n;i++){printf("%c\t %d\n",a[i].c,a[i].data);}}。

实验三.哈夫曼编码的贪心算法设计

实验三.哈夫曼编码的贪心算法设计

实验四 哈夫曼编码的贪心算法设计(4学时)[实验目的]1. 根据算法设计需要,掌握哈夫曼编码的二叉树结构表示方法;2. 编程实现哈夫曼编译码器;3. 掌握贪心算法的一般设计方法。

实验目的和要求(1)了解前缀编码的概念,理解数据压缩的基本方法;(2)掌握最优子结构性质的证明方法;(3)掌握贪心法的设计思想并能熟练运用(4)证明哈夫曼树满足最优子结构性质;(5)设计贪心算法求解哈夫曼编码方案;(6)设计测试数据,写出程序文档。

实验内容设需要编码的字符集为{d 1, d 2, …, dn },它们出现的频率为 {w 1, w 2, …, wn },应用哈夫曼树构造最短的不等长编码方案。

核心源代码#include <stdio.h>#include <stdlib.h>#include <string.h>typedef struct{unsigned int weight; //用来存放各个结点的权值unsigned int parent,LChild,RChild; //指向双亲、孩子结点的指针} HTNode, *HuffmanTree; //动态分配数组,存储哈夫曼树typedef char *HuffmanCode; //动态分配数组,存储哈夫曼编码∑=ji k k a//选择两个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'; //左分支标1a[i]++;}else{cd[--start]='0'; //右分支标0a[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);}void 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);}实验结果实验体会哈夫曼编码算法:每次将集合中两个权值最小的二叉树合并成一棵新二叉树,n-1次合并后,成为最终的一棵哈夫曼树。

哈夫曼编码贪心算法

哈夫曼编码贪心算法

哈夫曼编码贪心算法
一、哈夫曼编码
哈夫曼编码(Huffman Coding)是一种著名的数据压缩算法,也称作霍夫曼编码,由美国信息论家杰弗里·哈夫曼在1952年提出[1]。

哈夫曼编码可以有效地将资料压缩至最小,它的原理是将资料中出现频率最高的字元编码为最短的码字,而出现频率低的字元编码为较长的码字,从而显著提高了信息的保密性和容量。

二、贪心算法
贪心算法(Greedy Algorithm)是一种计算机算法,它试图找到一种满足条件的最佳解决方案,通常每一步都是做出在当前状态下最佳的选择,而不考虑将来可能发生的结果。

哈夫曼编码贪心算法是利用贪心算法来实现哈夫曼编码的。

该算法的步骤如下:
1. 首先统计出每一个字符出现的次数,并以此建立森林。

森林
中的每一棵树都用一个节点表示,每个节点的数值为字符出现的次数。

2. 从森林中挑选出两个出现次数最少的字符,将它们作为左右
子树合成一颗新的树,新树的根节点的数值为两个孩子节点的和。

3. 将新树加入森林中,并删除左右子树对应的原节点。

4. 重复上述步骤,直到森林中只剩一颗树,这颗树就是哈夫曼树。

5. 从哈夫曼树根节点出发,逐层往下搜索,左子节点编码为“0”,右子节点编码为“1”,最终得到每个字符的哈夫曼编码。

贪心算法实现Huffman编码

贪心算法实现Huffman编码

算法分析与设计实验报告第次实验附录:完整代码#include <iostream>#include <string>#include<stdio.h>#include <time.h>#include <iomanip>#include <vector>#include<algorithm>using namespace std;class Huffman{public:char elementChar;//节点元素int weight;//权重char s;//哈夫曼编码Huffman* parent;//父节点Huffman* leftChild;//左孩子Huffman* rightChild;//右孩子public:Huffman();Huffman(char a, int weight);bool operator < (const Huffman &m)const { return weight < m.weight;} };Huffman::Huffman(){this->s = ' ';this->elementChar = '*';//非叶子节点this->parent = this->leftChild = this->rightChild = NULL;}Huffman::Huffman(char a, int weight):elementChar(a),weight(weight) {this->s = ' ';this->elementChar = '*';//非叶子节点this->parent = this->leftChild = this->rightChild = NULL;}//递归输出哈夫曼值void huffmanCode(Huffman & h){if(h.leftChild == NULL && h.rightChild == NULL){//如果是叶子节点,输出器哈夫曼编码string s;Huffman temp = h;while(temp.parent != NULL){s = temp.s + s;temp = *temp.parent;}cout << h.elementChar << "的哈夫曼编码是:" << s << endl; return;}//左孩子huffmanCode(*h.leftChild);//右孩子huffmanCode(*h.rightChild);}int main(){int l,p=0;double q=0.0;clock_t start,end,over;start=clock();end=clock();over=end-start;start=clock();string huffmanStr;cout << "请输入一串字符序列:" << endl;cin >> huffmanStr;//得到字符串信息int i=0,j,n,m[100],h,k=0;char cha[100];n = huffmanStr.length();cout << "字符串总共有字符" << n << "个" << endl;for(int i = 0; i < n; i++){j = 0; h = 0;while(huffmanStr[i] != huffmanStr[j])j++;if(j == i){cha[k] = huffmanStr[i];cout << "字符" << cha[k] << "出现";}//如果j !=i 则略过此次循环elsecontinue;for(j = i; j < n; j++){if(huffmanStr[i] == huffmanStr[j])h++;}cout << h << "次" << endl;m[k] = h;k++;}//哈夫曼编码Huffman huffmanTemp;vector < Huffman > huffmanQueue;//初始化队列for(int i = 0; i < k; i++){huffmanTemp.elementChar = cha[i];huffmanTemp.weight = m[i];huffmanQueue.push_back(huffmanTemp);}//得到哈夫曼树所有节点int huffmanQueue_index = 0;sort(huffmanQueue.begin(), huffmanQueue.end());while(huffmanQueue.size() < 2 * k - 1){//合成最小两个节点的父节点huffmanTemp.weight = huffmanQueue[huffmanQueue_index].weight + huffmanQueue[huffmanQueue_index + 1].weight;huffmanQueue[huffmanQueue_index].s = '0';huffmanQueue[huffmanQueue_index + 1].s = '1';huffmanTemp.elementChar = '*';//将父节点加入队列huffmanQueue.push_back(huffmanTemp);sort(huffmanQueue.begin(), huffmanQueue.end());huffmanQueue_index += 2;}//把所有节点构造成哈夫曼树int step = 0;//步长while(step + 2 < 2 * k){for(int j = step + 1; j <= huffmanQueue.size(); j++){if(huffmanQueue[j].elementChar == '*' && huffmanQueue[j].leftChild == NULL && (huffmanQueue[j].weight == huffmanQueue[step].weight + huffmanQueue[step+1].weight)){huffmanQueue[j].leftChild = &huffmanQueue[step];huffmanQueue[j].rightChild = &huffmanQueue[step+1];huffmanQueue[step].parent = huffmanQueue[step+1].parent = &huffmanQueue[j]; break;}}step += 2;}//序列最后一个元素,即哈弗曼树最顶端的节点huffmanTemp = huffmanQueue.back();huffmanCode(huffmanTemp);for(l=0;l<1000000000;l++)p=p+l;end=clock();printf("The time is %6.3f",(double)(end-start-over)/CLK_TCK);return 0;}。

哈夫曼编码的贪心算法时间复杂度

哈夫曼编码的贪心算法时间复杂度

哈夫曼编码的贪心算法时间复杂度哈夫曼编码的贪心算法时间复杂度在信息技术领域中,哈夫曼编码是一种被广泛应用的数据压缩技术,它利用了贪心算法的思想来设计。

贪心算法是一种在每一步都选择当前状态下最优解的方法,从而希望通过一系列局部最优解达到全局最优解。

在哈夫曼编码中,这个想法被巧妙地运用,从而有效地实现了数据的高效压缩和解压缩。

哈夫曼编码是由大名鼎鼎的大卫·哈夫曼(David A. Huffman)在1952年提出的,它通过将频率最高的字符赋予最短的编码,最低的字符赋予最长的编码,从而实现了对数据的高效压缩。

这种编码技术在通信领域、存储领域和计算机科学领域都有着广泛的应用,是一种非常重要的数据处理技术。

在哈夫曼编码的实现过程中,贪心算法的时间复杂度是非常重要的。

时间复杂度是用来衡量算法所需时间的数量级,通常使用大O记号(O(n))来表示。

对于哈夫曼编码的贪心算法来说,其时间复杂度主要取决于以下几个步骤:1. 需要对数据进行统计,以获取每个字符出现的频率。

这个步骤的时间复杂度是O(n),其中n表示字符的数量。

在实际应用中,这个步骤通常由哈希表或统计排序来实现,因此时间复杂度可以控制在O(n)的数量级。

2. 接下来,需要构建哈夫曼树。

哈夫曼树是一种特殊的二叉树,它的构建过程需要将频率最低的两个节点合并成一个新的节点,然后再对新节点进行排序。

这个过程会持续n-1次,直到所有节点都被合并到一棵树中。

构建哈夫曼树的时间复杂度是O(nlogn),其中n表示字符的数量。

3. 根据哈夫曼树生成每个字符的编码。

这个过程涉及到对哈夫曼树进行遍历,并记录下每个字符对应的编码。

由于哈夫曼树的特性,每个字符的编码可以通过从根节点到叶子节点的路径来得到。

这个步骤的时间复杂度是O(n),因为对于每个字符都需要进行一次遍历。

哈夫曼编码的贪心算法时间复杂度主要由构建哈夫曼树的步骤决定,为O(nlogn)。

这意味着在实际应用中,哈夫曼编码的运行时间随着字符数量的增加而增加,并且增长速度为nlogn的数量级。

贪心算法知识点总结

贪心算法知识点总结

贪心算法知识点总结1. 基本原理贪心算法的基本原理是每一步都选择当前状态下的最优解,以期望最终得到全局最优解。

具体来说,贪心算法通常可以分为以下几个步骤:1)从问题的某个初始解出发2)采用一种迭代的方式,逐步将初始解进行优化3)每一步都是基于当前状态的最优选择来进行优化4)直到无法再进行优化,得到问题的最优解由于贪心算法每一步都要选择局部最优解,因此贪心算法通常具有高效性。

然而,贪心算法并不适用于所有问题,其结果不一定是全局最优解。

因此,在使用贪心算法时需要注意问题的特性和约束条件,以免得到错误的结果。

2. 适用情况贪心算法通常适用于满足以下条件的问题:1)问题的最优解满足“最优子结构”性质:即问题的最优解包含了其子问题的最优解2)问题的求解过程具有“贪心选择性”:即每一步都选择当前状态下的最优解,并不需要考虑未来的后果3)问题的约束条件可以通过局部最优选择满足全局最优解:即问题的解空间中存在一些局部最优解,可以通过一系列的局部最优解构建全局最优解在实际应用中,贪心算法通常用于求解最优化问题,如最小生成树、最短路径、任务调度等问题。

由于贪心算法的高效性,它通常能够在较短的时间内得到较为接近最优解的结果。

然而,贪心算法并不适用于所有问题,对于一些问题,贪心算法将得到错误的结果。

因此,在使用贪心算法时需要谨慎选择问题类型和约束条件,以避免错误的结果。

3. 贪心算法实例在下面的部分,我们将介绍一些常见的贪心算法实例,包括背包问题、活动安排问题、霍夫曼编码等。

3.1 背包问题背包问题是一个经典的优化问题,它包括0-1背包问题、分数背包问题等多种类型。

在0-1背包问题中,给定n种物品和一个容量为C的背包,每种物品i的重量为w[i],价值为v[i],求在不超过背包容量的情况下,如何选择物品放入背包,可以使得背包中的总价值最大。

对于0-1背包问题,贪心算法通常不能得到最优解。

然而,在分数背包问题中,贪心算法通常可以得到近似的最优解。

贪心算法-哈弗曼编码、汽车加油问题

贪心算法-哈弗曼编码、汽车加油问题

1.问题描述哈夫曼编码(贪心策略)——要求给出算法思想、编码程序和译码程序,对样本数据“哈夫曼编码实验数据.dat”,要求提交符号的具体编码以及编码后的文件。

2.求解问题的贪心算法描述压缩数据由以下步骤组成:a)检查字符在数据中的出现频率。

b)构建哈夫曼树。

c)创建哈夫曼编码表。

d)生成压缩后结果,由一个文件头和压缩后的数据组成。

3.算法实现的关键技巧1.最小堆两种基本操作:插入新元素,抽取最小元素。

(1)插入新元素:把该元素放在二叉树的末端,然后从该新元素开始,向根节点方向进行交换,直到它到达最终位置。

(2)抽取最小元素:把根节点取走。

然后把二叉树的末端节点放到根节点上,然而把该节点向子结点反复交换,直到它到达最终位置。

2. 构建哈夫曼树:a)把所有出现的字符作为一个节点(单节点树),把这些树组装成最小堆;b)从该优先级队列中连续抽取两个频率最小的树分别作为左子树,右子树,将他们合并成一棵树(频率=两棵树频率之和),然后把这棵树插回队列中。

c)重复步骤b,每次合并都将使优先级队列的尺寸减小1,直到最后队列中只剩一棵树为止,就是我们需要的哈夫曼树。

3.压缩数据: 遍历输入的文本,对每个字符,根据编码表依次把当前字符的编码写入到编码结果中去。

File Header(文件头):unsigned int size; 被编码的文本长度(字符数);unsigned char freqs[ NUM_CHARS ]; 字符频率表compressed; (Bits: 压缩后的数据);4.贪心选择性质&最优子结构性质证明:即证明最优前缀码问题具有弹性选择性质和最优子结构性质.(1)贪心选择性质设C是编码字符集,C中字符c的频率为f(c)。

又设x,y是C中具有最小频率的两个字符,则存在C的最优前缀码使x,y具有相同码长且仅最后一位编码不同。

证明:设二叉树T表示C的任意一个最优前缀码。

对C左适当修改得到T“,使得在新树中,x和y是最深叶子且为兄弟。

哈夫曼编码算法思想总结

哈夫曼编码算法思想总结

哈夫曼编码算法思想总结哈夫曼编码算法是一种基于字符出现频率的编码方式,其主要思想是通过建立一颗哈夫曼树来实现最优的编码方案。

哈夫曼编码算法在数据压缩领域广泛应用,在传输和存储数据时可以减小数据的体积,提高数据的传输效率。

哈夫曼编码算法的核心思想是根据字符出现的频率,对字符进行编码,使得出现频率较高的字符获得较短的编码,出现频率较低的字符获得较长的编码。

这样可以有效地减小编码后的数据长度,并且在解码的时候不会存在二义性。

哈夫曼编码算法的实现过程主要分为三个步骤:建立字符频率统计表、构造哈夫曼树、生成编码表。

首先,需要统计字符出现的频率。

扫描待编码的文本,记录每个字符出现的次数。

可以使用哈希表或者数组来实现字符与频率之间的映射关系。

其次,根据字符频率构造哈夫曼树。

通过扫描字符频率统计表,将每个字符及其频率作为叶子节点构建一颗树。

每个节点的权值为其子树中所有叶子节点的权值之和。

不断地选择权值最小的两个节点,将它们合并为一个新的节点,并更新权值。

重复这个过程,直到只剩下一个节点,即为哈夫曼树的根节点。

最后,生成编码表。

从哈夫曼树的根节点开始,向下遍历每个节点,标记左分支为0,右分支为1。

这样可以得到每个字符对应的二进制编码。

将字符及其对应的编码存储在编码表中,供解码时使用。

利用生成的编码表,可以将待编码的文本转化为哈夫曼编码。

每个字符根据编码表中的对应编码进行替换,得到编码后的二进制数据。

由于频率高的字符对应的编码较短,所以编码后的数据长度相比原始数据大大减小。

在解码时,根据哈夫曼树的结构和编码表的内容,可以还原出原始的文本数据。

从根节点开始,根据编码依次遍历哈夫曼树,根据0或者1选择左分支或者右分支。

当到达叶子节点时,找到对应的字符,并将其输出到解码后的文本中。

哈夫曼编码算法的优点是编码后的数据长度相对较短,可以实现高效的数据压缩。

在数据传输和存储过程中,可以减少带宽的占用,提高传输效率。

同时,哈夫曼编码算法的解码过程也相对简单,可以快速还原出原始的文本数据。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

0023算法笔记——【贪心算法】哈夫曼编码问题1、问题描述哈夫曼编码是广泛地用于数据文件压缩的十分有效的编码方法。

其压缩率通常在20%~90%之间。

哈夫曼编码算法用字符在文件中出现的频率表来建立一个用0,1串表示各字符的最优表示方式。

一个包含100,000个字符的文件,各字符出现频率不同,如下表所示。

有多种方式表示文件中的信息,若用0,1码表示字符的方法,即每个字符用唯一的一个0,1串表示。

若采用定长编码表示,则需要3位表示一个字符,整个文件编码需要300,000位;若采用变长编码表示,给频率高的字符较短的编码;频率低的字符较长的编码,达到整体编码减少的目的,则整个文件编码需要(45×1+13×3+12×3+16×3+9×4+5×4)×1000=224,000位,由此可见,变长码比定长码方案好,总码长减小约25%。

前缀码:对每一个字符规定一个0,1串作为其代码,并要求任一字符的代码都不是其他字符代码的前缀。

这种编码称为前缀码。

编码的前缀性质可以使译码方法非常简单;例如001011101可以唯一的分解为0,0,101,1101,因而其译码为aabe。

译码过程需要方便的取出编码的前缀,因此需要表示前缀码的合适的数据结构。

为此,可以用二叉树作为前缀码的数据结构:树叶表示给定字符;从树根到树叶的路径当作该字符的前缀码;代码中每一位的0或1分别作为指示某节点到左儿子或右儿子的“路标”。

从上图可以看出,表示最优前缀码的二叉树总是一棵完全二叉树,即树中任意节点都有2个儿子。

图a表示定长编码方案不是最优的,其编码的二叉树不是一棵完全二叉树。

在一般情况下,若C是编码字符集,表示其最优前缀码的二叉树中恰有|C|个叶子。

每个叶子对应于字符集中的一个字符,该二叉树有|C|-1个内部节点。

给定编码字符集C及频率分布f,即C中任一字符c以频率f(c)在数据文件中出现。

C的一个前缀码编码方案对应于一棵二叉树T。

字符c在树T中的深度记为d T(c)。

d T(c)也是字符c的前缀码长。

则平均码长定义为:使平均码长达到最小的前缀码编码方案称为C的最优前缀码。

"2、构造哈弗曼编码哈夫曼提出构造最优前缀码的贪心算法,由此产生的编码方案称为哈夫曼编码。

其构造步骤如下:(1)哈夫曼算法以自底向上的方式构造表示最优前缀码的二叉树T。

(2)算法以|C|个叶结点开始,执行|C|-1次的“合并”运算后产生最终所要求的树T。

(3)假设编码字符集中每一字符c的频率是f(c)。

以f为键值的优先队列Q用在贪心选择时有效地确定算法当前要合并的2棵具有最小频率的树。

一旦2棵具有最小频率的树合并后,产生一棵新的树,其频率为合并的2棵树的频率之和,并将新树插入优先队列Q。

经过n-1次的合并后,优先队列中只剩下一棵树,即所要求的树T。

构造过程如图所示:具体代码实现如下:(1),程序主文件1.>2.eight = f[i];3. w[i].tree = z;4. }5.6.//建优先队列7. MinHeap<Huffman<Type>> Q(n);8.for(int i=1; i<=n; i++) (w[i]);9.10.//反复合并最小频率树11. Huffman<Type> x,y;12.*13.for(int i=1; i<n; i++)14. {15. x = ();16. y = ();17. (0,,;18. += ;19. = z;20. (x);21. }23.|24. x = ();25.26.delete[] w;27.28.return ;29.}(2) 二叉树实现1.#include<iostream>ing namespace std;3.~4.5.template<class T>6.struct BTNode7.{8. T data;9. BTNode<T> *lChild,*rChild;10.11. BTNode()12. {13. lChild=rChild=NULL;14.'15. }16.17. BTNode(const T &val,BTNode<T> *Childl=NULL,BTNode<T> *Childr=NULL)18. {19. data=val;20. lChild=Childl;21. rChild=Childr;22. }23.24. BTNode<T>* CopyTree()25.·26. {27. BTNode<T> *nl,*nr,*nn;28.29.if(&data==NULL)30.return NULL;31.32. nl=lChild->CopyTree();33. nr=rChild->CopyTree();35. nn=new BTNode<T>(data,nl,nr);36.《37.return nn;38. }39.};40.41.42.template<class T>43.class BinaryTree44.{45.public:46. BTNode<T> *root;47.·48. BinaryTree();49. ~BinaryTree();50.51.void Pre_Order();52.void In_Order();53.void Post_Order();54.55.int TreeHeight()const;56.int TreeNodeCount()const;57.58.;59.void DestroyTree();60.void MakeTree(T pData,BinaryTree<T> leftTree,BinaryTree<T> rightTree);61.void Change(BTNode<T> *r);62.63.private:64.void Destroy(BTNode<T> *&r);65.void PreOrder(BTNode<T> *r);66.void InOrder(BTNode<T> *r);67.void PostOrder(BTNode<T> *r);68.69.,70.int Height(const BTNode<T> *r)const;71.int NodeCount(const BTNode<T> *r)const;72.};73.74.template<class T>75.BinaryTree<T>::BinaryTree()76.{77. root=NULL;79.80.&81.template<class T>82.BinaryTree<T>::~BinaryTree()83.{84.85.}86.87.template<class T>88.void BinaryTree<T>::Pre_Order()89.{90. PreOrder(root);91.,92.}93.94.template<class T>95.void BinaryTree<T>::In_Order()96.{97. InOrder(root);98.}99.100.template<class T>101.void BinaryTree<T>::Post_Order() 102.—103.{104. PostOrder(root);105.}106.107.template<class T>108.int BinaryTree<T>::TreeHeight()const 109.{110.return Height(root);111.}112.113.、114.template<class T>115.int BinaryTree<T>::TreeNodeCount()const 116.{117.return NodeCount(root);118.}119.120.template<class T>121.void BinaryTree<T>::DestroyTree()123. Destroy(root);124.,125.}126.127.template<class T>128.void BinaryTree<T>::PreOrder(BTNode<T> *r)129.{130.if(r!=NULL)131. {132. cout<<r->data<<' ';133. PreOrder(r->lChild);134. PreOrder(r->rChild);135.~136. }137.}138.139.template<class T>140.void BinaryTree<T>::InOrder(BTNode<T> *r)141.{142.if(r!=NULL)143. {144. InOrder(r->lChild);145. cout<<r->data<<' ';146.—147. InOrder(r->rChild);148. }149.}150.151.template<class T>152.void BinaryTree<T>::PostOrder(BTNode<T> *r)153.{154.if(r!=NULL)155. {156. PostOrder(r->lChild);157."158. PostOrder(r->rChild);159. cout<<r->data<<' ';160. }161.}162.163.template<class T>164.int BinaryTree<T>::NodeCount(const BTNode<T> *r)const 165.{166.if(r==NULL)167.return 0;168.¥169.else170.return 1+NodeCount(r->lChild)+NodeCount(r->rChild);171.}172.173.template<class T>174.int BinaryTree<T>::Height(const BTNode<T> *r)const175.{176.if(r==NULL)177.return 0;178.else179.?180. {181.int lh,rh;182. lh=Height(r->lChild);183. rh=Height(r->rChild);184.return 1+(lh>rhlh:rh);185. }186.}187.188.template<class T>189.void BinaryTree<T>::Destroy(BTNode<T> *&r)190.-191.{192.if(r!=NULL)193. {194. Destroy(r->lChild);195. Destroy(r->rChild);196.delete r;197. r=NULL;198. }199.}200.201.#202.template<class T>203.void BinaryTree<T>::Change(BTNode<T> *r)//将二叉树bt所有结点的左右子树交换204.{205. BTNode<T> *p;206.if(r){207. p=r->lChild;208. r->lChild=r->rChild;209. r->rChild=p; //左右子女交换210. Change(r->lChild); //交换左子树上所有结点的左右子树211. Change(r->rChild); //交换右子树上所有结点的左右子树212.~213. }214.}215.216.template<class T>217.void BinaryTree<T>::MakeTree(T pData,BinaryTree<T> leftTree,BinaryTree<T> rightTree) 218.{219. root = new BTNode<T>();220. root->data = pData;221. root->lChild = ;222. root->rChild = ;223.]224.}(3) 最小堆实现1.#include <iostream>ing namespace std;3.template<class T>4.class MinHeap5.{6.private:7. T *heap; //元素数组,0号位置也储存元素8.!9.int CurrentSize; //目前元素个数10.int MaxSize; //可容纳的最多元素个数11.12.void FilterDown(const int start,const int end); //自上往下调整,使关键字小的节点在上13.void FilterUp(int start); //自下往上调整14.15.public:16. MinHeap(int n=1000);17. ~MinHeap();18.bool Insert(const T &x); //插入元素19.|20.21. T RemoveMin(); //删除最小元素22. T GetMin(); //取最小元素23.24.bool IsEmpty() const;25.bool IsFull() const;26.void Clear();27.};28.29.template<class T>30.,31.MinHeap<T>::MinHeap(int n)32.{33. MaxSize=n;34. heap=new T[MaxSize];35. CurrentSize=0;36.}37.38.template<class T>39.MinHeap<T>::~MinHeap()40.{41.!42.delete []heap;43.}44.45.template<class T>46.void MinHeap<T>::FilterUp(int start) //自下往上调整47.{48.int j=start,i=(j-1)/2; //i指向j的双亲节点49. T temp=heap[j];50.51.while(j>0)52.]53. {54.if(heap[i]<=temp)55.break;56.else57. {58. heap[j]=heap[i];59. j=i;60. i=(i-1)/2;61. }62. }63.'64. heap[j]=temp;65.}66.67.template<class T>68.void MinHeap<T>::FilterDown(const int start,const int end) //自上往下调整,使关键字小的节点在上69.{70.int i=start,j=2*i+1;71. T temp=heap[i];72.while(j<=end)73. {74.]75.if( (j<end) && (heap[j]>heap[j+1]) )76. j++;77.if(temp<=heap[j])78.break;79.else80. {81. heap[i]=heap[j];82. i=j;83. j=2*j+1;84. }85.*86. }87. heap[i]=temp;88.}89.90.template<class T>91.bool MinHeap<T>::Insert(const T &x)92.{93.if(CurrentSize==MaxSize)94.return false;95.96.$97. heap[CurrentSize]=x;98. FilterUp(CurrentSize);99.100. CurrentSize++;101.return true;102.}103.104.template<class T>105.T MinHeap<T>::RemoveMin( )106.{107.…108. T x=heap[0];109. heap[0]=heap[CurrentSize-1];110.111. CurrentSize--;112. FilterDown(0,CurrentSize-1); //调整新的根节点113.114.return x;115.}116.117.template<class T>118.!119.T MinHeap<T>::GetMin()120.{121.return heap[0];122.}123.124.template<class T>125.bool MinHeap<T>::IsEmpty() const126.{127.return CurrentSize==0;128.}129.130.template<class T>131.bool MinHeap<T>::IsFull() const132.{133.return CurrentSize==MaxSize;134.}135.136.template<class T>137.void MinHeap<T>::Clear()138.{139. CurrentSize=0;140.}3、贪心选择性质二叉树T表示字符集C的一个最优前缀码,证明可以对T作适当修改后得到一棵新的二叉树T”,在T”中x和y是最深叶子且为兄弟,同时T”表示的前缀码也是C的最优前缀码。

相关文档
最新文档