算法笔记——【贪心算法】哈夫曼编码问题
贪心算法构造哈夫曼树
软件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);}}。
哈夫曼编码问题总结
哈夫曼编码问题总结
哈夫曼编码是一种常见的数据压缩算法,但在实践中常常遇到各种问题。
以下是哈夫曼编码问题的总结:
1. 频率表不准确:哈夫曼编码的核心是根据字符出现的频率构建树结构,如果频率表不准确,树的构建就会出现问题。
因此,在使用哈夫曼编码前,需要确保频率表的准确性。
2. 编码长度不一:哈夫曼编码是一种变长编码,不同字符的编码长度不一定相同。
如果出现某个字符的编码长度过长,就会导致压缩效果变差。
为了避免这种情况,可以使用贪心算法来构建哈夫曼树,使得每个字符的编码长度尽可能短。
3. 解码效率低:在解码哈夫曼编码时,需要遍历整个哈夫曼树,查找对应的字符。
如果哈夫曼树过大,解码效率就会变低。
为了提高解码效率,可以使用哈夫曼编码的反向索引表来加速查找。
4. 压缩效果不佳:尽管哈夫曼编码可以大幅度减少数据的存储空间,但在某些情况下,压缩效果可能不如其他算法。
例如,对于随机分布的数据,哈夫曼编码的效果很差。
因此,在选择数据压缩算法时,需要根据具体情况进行选择。
总之,哈夫曼编码是一种强大的数据压缩算法,但在实践中需要注意以上问题。
只有对这些问题有充分的了解和掌握,才能更好地运用哈夫曼编码来实现数据压缩。
- 1 -。
实验三.哈夫曼编码的贪心算法设计
实验四 哈夫曼编码的贪心算法设计(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次合并后,成为最终的一棵哈夫曼树。
哈夫曼编码贪心算法时间复杂度
哈夫曼编码贪心算法时间复杂度
哈夫曼编码的贪心算法时间复杂度为O(nlogn),其中n为待编
码的字符数量。
算法的主要步骤包括构建哈夫曼树和生成编码表两部分。
构建哈夫曼树的时间复杂度为O(nlogn),其中n为待编码的字
符数量。
构建哈夫曼树的过程涉及到对字符频次列表进行排序,并不断合并频次最小的两个节点,直至只剩下一个节点作为根节点。
排序的时间复杂度为O(nlogn),每次合并两个节点的时间复杂度为O(logn)。
因此,构建哈夫曼树的总时间复杂度为
O(nlogn)。
生成编码表的时间复杂度同样为O(nlogn),其中n为待编码的字符数量。
生成编码表的过程是遍历哈夫曼树的每个节点,并记录下每个字符所对应的编码。
由于哈夫曼树的每个叶子节点代表一个字符,因此遍历哈夫曼树的时间复杂度为O(n),并
且遍历过程的时间复杂度与树的高度相关,由于哈夫曼树是一个二叉树,因此树的高度为O(logn)。
因此,生成编码表的总
时间复杂度为O(nlogn)。
综上所述,哈夫曼编码的贪心算法的时间复杂度为O(nlogn)。
哈夫曼编码贪心算法
哈夫曼编码贪心算法
一、哈夫曼编码
哈夫曼编码(Huffman Coding)是一种著名的数据压缩算法,也称作霍夫曼编码,由美国信息论家杰弗里·哈夫曼在1952年提出[1]。
哈夫曼编码可以有效地将资料压缩至最小,它的原理是将资料中出现频率最高的字元编码为最短的码字,而出现频率低的字元编码为较长的码字,从而显著提高了信息的保密性和容量。
二、贪心算法
贪心算法(Greedy Algorithm)是一种计算机算法,它试图找到一种满足条件的最佳解决方案,通常每一步都是做出在当前状态下最佳的选择,而不考虑将来可能发生的结果。
哈夫曼编码贪心算法是利用贪心算法来实现哈夫曼编码的。
该算法的步骤如下:
1. 首先统计出每一个字符出现的次数,并以此建立森林。
森林
中的每一棵树都用一个节点表示,每个节点的数值为字符出现的次数。
2. 从森林中挑选出两个出现次数最少的字符,将它们作为左右
子树合成一颗新的树,新树的根节点的数值为两个孩子节点的和。
3. 将新树加入森林中,并删除左右子树对应的原节点。
4. 重复上述步骤,直到森林中只剩一颗树,这颗树就是哈夫曼树。
5. 从哈夫曼树根节点出发,逐层往下搜索,左子节点编码为“0”,右子节点编码为“1”,最终得到每个字符的哈夫曼编码。
huffman编码的基本原理
huffman编码的基本原理
Huffman编码是一种基于贪心算法的压缩算法,其基本原理是将出现频率较高的字符用较短的编码表示,而将出现频率较低的字符用较长的编码表示,从而达到数据压缩的目的。
具体来说,Huffman编码的过程分为两步:首先统计所有字符出现的频率,并将其构建成一个树状结构;其次,根据该树状结构为每个字符生成对应的编码。
在构建树状结构时,我们可以使用最小堆或者优先队列等数据结构来实现。
具体来说,我们可以首先将所有字符出现的频率存入堆中,然后每次从堆中选取两个频率最小的字符节点,将它们合并成一个新的节点,并将其频率设为两个节点频率之和,最后再将新节点加入堆中。
这个过程将持续到堆中只剩下一个节点为止,这个节点即为哈夫曼树的根节点。
生成编码时,从根节点出发,每次遇到左孩子将编码加上0,每次遇到右孩子将编码加上1。
最终,每个字符都可以通过该树状结构得到唯一的编码。
编码的长度即为该字符在哈夫曼树中所处的深度。
Huffman编码的优点是可以根据数据本身的特点来生成最优编码,从而达到较好的压缩效果。
其缺点是需要占用额外的存储空间来存储哈夫曼树,同时编码和解码的速度较慢。
- 1 -。
贪心算法实现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.问题描述哈夫曼编码(贪心策略)——要求给出算法思想、编码程序和译码程序,对样本数据“哈夫曼编码实验数据.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是最深叶子且为兄弟。
哈夫曼编码算法与分析
算法与分析1.哈夫曼编码是广泛地用于数据文件压缩的十分有效的编码方法。
给出文件中各个字符出现的频率,求各个字符的哈夫曼编码方案。
2.给定带权有向图G =(V,E),其中每条边的权是非负实数。
另外,还给定V中的一个顶点,称为源。
现在要计算从源到所有其他各顶点的最短路长度。
这里路的长度是指路上各边权之和。
3.设G =(V,E)是无向连通带权图,即一个网络。
E中每条边(v,w)的权为c[v][w]。
如果G的子图G’是一棵包含G的所有顶点的树,则称G’为G的生成树。
生成树上各边权的总和称为该生成树的耗费。
在G的所有生成树中,耗费最小的生成树称为G的最小生成树。
求G的最小生成树。
求解问题的算法原理:1.最优装载哈夫曼编码1.1前缀码对每一个字符规定一个0,1串作为其代码,并要求任一字符的代码都不是其它字符代码的前缀,这种编码称为前缀码。
编码的前缀性质可以使译码方法非常简单。
表示最优前缀码的二叉树总是一棵完全二叉树,即树中任一结点都有2个儿子结点。
平均码长定义为:B(T)=∑∈CcTcdcf)()(f(c):c的码长,dt(c):c的深度使平均码长达到最小的前缀码编码方案称为给定编码字符集C的最优前缀码。
1.2构造哈夫曼编码哈夫曼提出构造最优前缀码的贪心算法,由此产生的编码方案称为哈夫曼编码。
哈夫曼算法以自底向上的方式构造表示最优前缀码的二叉树T。
算法以|C|个叶结点开始,执行|C|-1次的“合并”运算后产生最终所要求的树T。
编码字符集中每一字符c的频率是f(c)。
以f为键值的优先队列Q用在贪心选择时有效地确定算法当前要合并的2棵具有最小频率的树。
一旦2棵具有最小频率的树合并后,产生一棵新的树,其频率为合并的2棵树的频率之和,并将新树插入优先队列Q。
经过n-1次的合并后,优先队列中只剩下一棵树,即所要求的树T。
可用最小堆实现优先队列Q。
2.单源最短路径Dijkstra算法是解单源最短路径问题的贪心算法。
其基本思想是,设置顶点集合S并不断地作贪心选择来扩充这个集合。
huffman编码例题
huffman编码例题Huffman编码是一种流行的数据压缩技术,也是许多压缩软件中使用的算法。
它可以通过建立权值树来生成对应的固定长度编码,称为Huffman编码。
在本文中,我们将介绍Huffman编码的原理以及一个具体的例题。
一、Huffman编码原理Huffman编码的实现原理是基于贪心算法。
它的目的是将出现频率较高的字符用较短的编码表示,而将出现频率较低的字符用较长的编码表示,以达到压缩数据的目的。
具体实现步骤如下:1.统计每个字符出现的频率。
2.建立哈夫曼树,每个节点代表一个字符,节点的权重为字符出现的频率。
3.对哈夫曼树进行遍历,为每个字符生成对应的Huffman编码。
4.将字符串中的每个字符替换成对应的Huffman编码。
二、Huffman编码例题假设有一个字符串"hello world",请编写程序进行Huffman编码和解码。
1统计每个字符出现的频率 h:1 e:1 l:3 o:2 w:1 r:1 d:12建立哈夫曼树从频率最小的字符开始,依次合并至根节点,得到以下哈夫曼树:11/ \5 6/ \ / \2 3 3 3/ / \h r d3生成Huffman编码从根节点开始遍历哈夫曼树,向左走为"0",向右走为"1",生成以下Huffman编码: h: 100 e: 1010 l: 00 o: 1011 w: 1100 r: 1101 d: 11104进行编码和解码使用步骤三中的编码表,将字符串"hello world"分别编码为: 101000001111001011111001101000011010111011100解码时,从根节点开始依次读取编码,遇到"0"则向左走,遇到"1"则向右走,直到读取完整个编码,找到对应的字符。
将编码解析后得到的二进制数转成对应的字符,即可得到原字符串"hello world"。
哈夫曼编码的贪心算法时间复杂度
哈夫曼编码是一种广泛应用于数据压缩领域的编码方式,而哈夫曼编码的贪心算法是实现这一编码方式的重要方法之一。
在本文中,我将深入探讨哈夫曼编码及其贪心算法的时间复杂度,并就此展开全面评估。
让我们简要回顾一下哈夫曼编码的基本概念。
哈夫曼编码是一种变长编码方式,通过将出现频率高的字符用较短的编码表示,而将出现频率低的字符用较长的编码表示,从而实现数据的有效压缩。
在这一编码方式中,贪心算法被广泛应用于构建哈夫曼树,以实现最优编码方案的选择。
那么,接下来我们将重点关注哈夫曼编码的贪心算法时间复杂度。
哈夫曼编码的贪心算法的时间复杂度主要取决于两个方面:构建哈夫曼树的时间复杂度和编码字符串的时间复杂度。
让我们来看构建哈夫曼树的时间复杂度。
在哈夫曼编码的贪心算法中,构建哈夫曼树的时间复杂度主要取决于构建最小堆(或最大堆)以及合并节点的操作。
在构建最小堆的过程中,需要对所有字符按照其频率进行排序,并将其依次插入最小堆中,这一操作的时间复杂度为O(nlogn)。
而在合并节点的过程中,需要不断从最小堆中取出两个频率最小的节点,并将其合并为一个新节点,然后再将新节点插入最小堆中,这一操作需要进行n-1次,所以合并节点的时间复杂度为O(nlogn)。
构建哈夫曼树的时间复杂度为O(nlogn)。
我们来看编码字符串的时间复杂度。
在使用哈夫曼编码对字符串进行编码时,需要根据构建好的哈夫曼树来进行编码,这一过程的时间复杂度主要取决于字符串的长度和哈夫曼树的深度。
由于哈夫曼树是一个二叉树,所以在最坏情况下,编码一个字符的时间复杂度为O(n),其中n为哈夫曼树的深度。
编码字符串的时间复杂度为O(kn),其中k 为字符串的长度。
哈夫曼编码的贪心算法的时间复杂度主要包括构建哈夫曼树的时间复杂度和编码字符串的时间复杂度,其中构建哈夫曼树的时间复杂度为O(nlogn),编码字符串的时间复杂度为O(kn)。
哈夫曼编码的贪心算法的时间复杂度为O(nlogn+kn)。
贪心法求哈夫曼编码
实验题目:设需要编码的字符集为{d1, d2, …, dn},它们出现的频率为{w1, w2, …, wn},应用哈夫曼树构造最短的不等长编码方案。
实验目的:(1)了解前缀编码的概念,理解数据压缩的基本方法;(2)掌握最优子结构性质的证明方法;(3)掌握贪心法的设计思想并能熟练运用。
实验内容:实验代码:#include <iostream>using namespace std;/** 霍夫曼树结构*/class HuffmanTree{public:unsigned int Weight, Parent, lChild, rChild;};typedef char **HuffmanCode;/** 从结点集合中选出权值最小的两个结点* 将值分别赋给s1和s2*/void Select(HuffmanTree* HT,int Count,int *s2,int *s1){unsigned int temp1=0;unsigned int temp2=0;unsigned int temp3;for(int i=1;i<=Count;i++){if(HT[i].Parent==0){if(temp1==0){temp1=HT[i].Weight;(*s1)=i;}else{if(temp2==0){temp2=HT[i].Weight;(*s2)=i;if(temp2<temp1){temp3=temp2;temp2=temp1;temp1=temp3;temp3=(*s2);(*s2)=(*s1);(*s1)=temp3;}}else{if(HT[i].Weight<temp1){temp2=temp1;temp1=HT[i].Weight;(*s2)=(*s1);(*s1)=i;}if(HT[i].Weight>temp1&&HT[i].Weight<temp2){temp2=HT[i].Weight;(*s2)=i;}}}}}}/** 霍夫曼编码函数*/void HuffmanCoding(HuffmanTree * HT,HuffmanCode * HC,int *Weight,int Count){int i;int s1,s2;int TotalLength;char* cd;unsigned int c;unsigned int f;int start;if(Count<=1) return;TotalLength=Count*2-1;HT = new HuffmanTree[(TotalLength+1)*sizeof(HuffmanTree)];for(i=1;i<=Count;i++){HT[i].Parent=0;HT[i].rChild=0;HT[i].lChild=0;HT[i].Weight=(*Weight);Weight++;}for(i=Count+1;i<=TotalLength;i++){HT[i].Weight=0;HT[i].Parent=0;HT[i].lChild=0;HT[i].rChild=0;}//建造霍夫曼树for(i=Count+1;i<=TotalLength;++i){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;}//输出霍夫曼编码(*HC)=(HuffmanCode)malloc((Count+1)*sizeof(char*));cd = new char[Count*sizeof(char)];cd[Count-1]='\0';for(i=1;i<=Count;++i){start=Count-1;for(c = i,f = HT[i].Parent; f != 0; c = f, f = HT[f].Parent){if(HT[f].lChild == c)cd[--start]='0';elsecd[--start]='1';(*HC)[i] = new char [(Count-start)*sizeof(char)];strcpy((*HC)[i], &cd[start]);}}delete [] HT;delete [] cd;}/** 在字符串中查找某个字符* 如果找到,则返回其位置*/int LookFor(char *str, char letter, int count){int i;for(i=0;i<count;i++){if(str[i]==letter) return i;}return -1;}void OutputWeight(char *Data,int Length,char **WhatLetter,int **Weight,int *Count){int i;char* Letter = new char[Length];int* LetterCount = new int[Length];int AllCount=0;int Index;int Sum=0;float Persent=0;for(i=0;i<Length;i++){if(i==0){Letter[0]=Data[i];LetterCount[0]=1;AllCount++;}else{Index=LookFor(Letter,Data[i],AllCount);if(Index==-1){Letter[AllCount]=Data[i];LetterCount[AllCount]=1;AllCount++;}else{LetterCount[Index]++;}}}for(i=0;i<AllCount;i++){Sum=Sum+LetterCount[i];}(*Weight) = new int[AllCount];(*WhatLetter) = new char[AllCount];for(i=0;i<AllCount;i++){Persent=(float)LetterCount[i]/(float)Sum;(*Weight)[i]=(int)(100*Persent);(*WhatLetter)[i]=Letter[i];}(*Count)=AllCount;delete [] Letter;delete [] LetterCount;}int main(){HuffmanTree * HT = NULL;HuffmanCode HC;char Data[100];char *WhatLetter;int *Weight;int Count;cout<<"请输入一行文本数据:"<<endl;cin>>Data;cout<<endl;OutputWeight(Data,strlen(Data),&WhatLetter,&Weight,&Count); HuffmanCoding(HT, &HC, Weight, Count);cout<<"字符出现频率编码结果"<<endl;for(int i = 0; i<Count; i++){cout<<WhatLetter[i]<<" ";cout<<Weight[i]<<"%\t";cout<<HC[i+1]<<endl;}cout<<endl;system("pause");return 0;}实验结果截图:哈夫曼算法描述:(1)初始化:将初始森林的各根结点(双亲)和左右孩子指针置为-1;(2)输入叶子权:叶子在向量T的前n个分量中,构成初始森林的n个根结点;(3)合并:对森林中的树进行n-1次合并,共产生n-1个新结点,依次放入向量T的第i 个分量(n<=i<=m-1)中,每次合并的步骤是:a、在当前森林的所有结点中,选取具有最小权值和次小权值的两个结点,分别用p1和p2记住这两个根节点在向量T中的下标;b、将根为T[p1]和T[p2]的两棵树合并,使其成为新结点T[i]的左右孩子,得到一棵以新结点T[i]为根的二叉树若取pi为叶结点的权,取编码长度li为叶结点的路径长度,则∑ pi ⨯li最小的问题就是带权路径长度最小的哈夫曼树的构造问题。
Python算法之哈夫曼编码
Python算法之哈夫曼编码问题:哈夫曼编码,英文名称Huffman Coding,有时也翻译为霍夫曼编码,在1952年提出的,是最好的编码方式。
哈夫曼编码在电子通讯方面有着重要的应用,同时也广泛应用于数据压缩,其压缩率通常在20%~90%之间赫夫曼码是可变字长编码(VLC)的一种。
哈夫曼树是最优二叉树,带权路径长度最小的二叉树。
原理:假设有几个数字40,10,20,16,14。
首先将这五个数字按照从小到大的顺序排列:10, 14,16,20, 40。
构建哈夫曼树:1.首先选取10,142.重新排序:16,20,24,403.重新排序24,36,40,604.按照二叉树左0右1,构建哈夫曼树所以最终得到数字10的编码为100,数字14的编码为101,数字16的编码为110,数字20的编码为111,数字40的编码为0。
代码:from heapq import *inp = input('请输入要构建哈夫曼树的字符串:')# 统计每个字符出现的频率并生成字典def generate_dict(s): dic = {} for i in s: if i not in dic: dic[i] = 1 else: dic[i] += 1 return dicdic = generate_dict(inp)# 节点类class Node: def __init__(self, name=None, weight=None): = name self.weight = weight self.parent = None self.left = None self.right = None self.id = None # 自定义类的比较def __lt__(self, other): return int(self.weight) < int(other.weight)# 按权值排序def sort(list): return sorted(list, key=lambda Node: Node.weight)def generate_node2(dic): lis = [] for i in dic: newNode = Node(i, dic[i]) heappush(lis, newNode) return lislis = generate_node2(dic)# Huffman编码2使用堆的方式def HuffmanTree2(lis): global id while len(lis) != 1: a = heappop(lis) b = heappop(lis) new = Node() new.weight = a.weight + b.weight new.left, new.right = a, b a.parent = new b.parent = new heappush(lis, new) return lislis = HuffmanTree2(lis)node = lis[0]# 定义前序遍历方法并执行一定的操作def pre_order(root, code): if root is None: code = code[:-1] return pre_order(root.left, code + '0') if is not None: print(, '的权重为', root.weight, '编码为', code) pre_order(root.right, code + '1')code = ''print('构造的哈夫曼树为:')pre_order(node, code)运行结果:请输入要构建哈夫曼树的字符串:12908755343298构造的哈夫曼树为:1 的权重为 1 编码为 0007 的权重为 1 编码为 0015 的权重为 2 编码为 0109 的权重为 2 编码为 0112 的权重为 2 编码为 1000 的权重为 1 编码为 10104 的权重为 1 编码为 10118 的权重为 2 编码为1103 的权重为 2 编码为 111。
哈夫曼编码-贪心算法
淮海工学院计算机工程学院实验报告书课程名:《算法分析与设计》题目:实验3 贪心算法哈夫曼编码班级:软件081班学号:**********名:***实验3 贪心算法实验目的和要求(1)了解前缀编码的概念,理解数据压缩的基本方法; (2)掌握最优子结构性质的证明方法; (3)掌握贪心法的设计思想并能熟练运用 (4)证明哈夫曼树满足最优子结构性质; (5)设计贪心算法求解哈夫曼编码方案; (6)设计测试数据,写出程序文档。
实验内容设需要编码的字符集为{d 1, d 2, …, dn },它们出现的频率为{w 1, w 2, …, wn },应用哈夫曼树构造最短的不等长编码方案。
实验环境Turbo C 或VC++ 实验学时2学时,必做实验 数据结构与算法typedef char *HuffmanCode; //动态分配数组,存储哈夫曼编码typedef struct {unsigned int weight; //用来存放各个结点的权值unsigned int parent,LChild,RChild; //指向双亲、孩子结点的指针 } HTNode, *HuffmanTree; //动态分配数组,存储哈夫曼树核心源代码#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)∑=ji k k aint 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、s2Select(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);}实验结果字符 1 2 3 4 5 6 7 8 平均码长权值0.32 0.16 0.07 0.22 0.23哈夫曼编码00 010 011 11 10 2.23等长编码000 001 010 011 100 3.00权值0.18 0.20 0.04 0.13 0.16 0.29哈夫曼编码11 10 0001 0000 001 01 2.5等长编码000 001 010 011 100 101 3.00权值0.17 0.8 0.23 0.04 0.35 0.28 0.01哈夫曼编码010 0110 11 01110 00 10 01111 2.8等长编码000 001 010 011 100 101 110 3.00权值0.27 0.09 0.03 0.12 0.05 0.24 0.16 0.04哈夫曼编码01 111 00101 110 0011 10 000 00100 2.68等长编码000 001 010 011 100 101 110 111 3.00说明:1,由表可知,哈夫曼编码的平均码长要比等长编码短。
分治法解决合并排序问题及动态规划解决矩阵连乘和最长公共子序列问题及贪心法解决哈夫曼编码问题
分治法解决合并排序问题及动态规划解决矩阵连乘和最长公共⼦序列问题及贪⼼法解决哈夫曼编码问题分治法解决合并排序问题及动态规划解决矩阵连乘和最长公共⼦序列问题及贪⼼法解决哈夫曼编码问题⼀、课程设计⽬的本次课程设计可以说是我们学完《计算机算法设计与分析》这门课程后的⼀次综合性训练。
本课程设计的训练的⽬的是:1、巩固和掌握计算机算法设计和分析课程的基础知识。
2、培养使⽤计算机基本算法解决实际问题的能⼒。
3、提升使⽤程序设计语⾔对算法程序的开发、调试和测试能⼒。
4、对⼀定的实际问题,能够设计求解算法并分析算法的效率。
5、提⾼综合运⽤算法、程序设计语⾔等能⼒。
6、掌握⽂档的书写能⼒。
⼆、课程设计内容1、分治法(1)合并排序2、动态规划(1)矩阵连乘(2)最长公共⼦序列3、贪⼼法(1)哈夫曼编码三、概要设计1、分治法基本思想:将规模为n的问题分解为k个规模较⼩的⼦问题,使这些⼦问题相互独⽴可分别求解,再将k个⼦问题的解合并成原问题的解。
如⼦问题的规模仍很⼤,则反复分解直到问题⼩到可直接求解为⽌。
在分治法中,⼦问题的解法通常与原问题相同。
(1)合并排序[问题描述]将n个元素排成⾮递减顺序。
[算法思路]若n为1,算法终⽌;否则,将n个待排元素分割成k(k=2)个⼤致相等⼦集合A, B, 对每⼀个⼦集合分别递归排序,再将排好序的⼦集归并为⼀个集合。
2、动态规划基本思想:将问题的求解过程化为多步选择,在每⼀步选择上,列出各种可能的结果(各⼦问题的可⾏解),舍去那些肯定不能成为最优解的局部解。
最后⼀步得到的解必是最优解。
求解过程多为⾃底向上,求解过程产⽣多个选择序列, 下⼀步的选择依赖上⼀步的结果,总能得到最优解。
(1)矩阵连乘[问题描述]给定n个矩阵{A1,…,An},其中Ai与A(i-1)是可相乘的。
确定⼀个计算次序,使计算矩阵连乘积A1…An所需计算量最少。
例如,三个矩阵连乘,两种计算顺序(A*B)*C,A*(B*C)。
哈夫曼树构造方法
哈夫曼树构造方法哈夫曼树(Huffman Tree)是一种广泛应用于数据压缩和编码的二叉树结构。
它是一种最优二叉树,即带权路径长度最短的二叉树。
哈夫曼树的构造方法主要有两种:贪心算法和分治算法。
1. 贪心算法:哈夫曼树的贪心算法是一种自底向上(从叶子节点到根节点)的构造方法。
首先,根据给定的权值列表,将每个权值看作一个独立的节点,并按照权值从小到大的顺序构建一个森林。
然后,从森林中选择权值最小的两个节点(可以使用最小堆来实现),将它们合并为一个新的节点,并将新节点的权值设为两个被合并节点的权值之和。
将新节点插入到森林中,并移除原来的两个节点。
重复上述步骤,直到森林中只有一个节点为止,该节点就是哈夫曼树的根节点。
贪心算法构造哈夫曼树的时间复杂度为O(nlogn),n为节点数量。
2. 分治算法:哈夫曼树的分治算法是一种自顶向下(从根节点到叶子节点)的构造方法。
首先,将给定的权值列表按照权值从小到大的顺序排序。
然后,将权值最小的两个节点合并为一个新的节点,并将新节点的权值设为两个被合并节点的权值之和。
将新节点插入到排序后的列表中,并移除原来的两个节点。
重复上述步骤,直到列表中只有一个节点为止,该节点就是哈夫曼树的根节点。
分治算法构造哈夫曼树的时间复杂度为O(n^2),n为节点数量。
无论是贪心算法还是分治算法,构造出的哈夫曼树都具有最优性质,即带权路径长度最短。
由于贪心算法的时间复杂度较低,因此在实际应用中更为常用。
另外,构造哈夫曼树的方法除了贪心算法和分治算法外,还可以使用动态规划等其他方法。
对于哈夫曼树的应用,最常见的是数据压缩和编码。
哈夫曼树可以根据字符出现的频率构建对应的编码表,将频率高的字符用较短的编码表示,将频率低的字符用较长的编码表示,从而实现对数据的压缩。
在压缩的过程中,利用哈夫曼树可以实现对数据的高效编码和解码。
此外,哈夫曼树还有其他应用,比如在路由表的构建和图像压缩等领域也有广泛应用。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 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)4d4.cpp,程序主文件[cpp]view plain copy1.//4d4 贪心算法哈夫曼算法2.#include "stdafx.h"3.#include "BinaryTree.h"4.#include "MinHeap.h"5.#include <iostream>ing namespace std。
7.8.const int N = 6。
9.10.template<class Type> class Huffman。
11.12.template<class Type>13.BinaryTree<int> HuffmanTree(Type f[],int n)。
14.15.template<class Type>16.class Huffman17.{18.friend BinaryTree<int> HuffmanTree(Type[],int)。
19.public:20. operator Type() const21. {22.return weight。
23. }24.//private:25. BinaryTree<int> tree。
26. Type weight。
27.}。
28.29.int main()30.{31.char c[] = {'0','a','b','c','d','e','f'}。
32.int f[] = {0,45,13,12,16,9,5}。
//下标从1开始33. BinaryTree<int> t = HuffmanTree(f,N)。
34.35. cout<<"各字符出现的对应频率分别为:"<<endl。
36.for(int i=1。
i<=N。
i++)37. {38. cout<<c[i]<<":"<<f[i]<<" "。
39. }40. cout<<endl。
41.42. cout<<"生成二叉树的前序遍历结果为:"<<endl。
43. t.Pre_Order()。
44. cout<<endl。
45.46. cout<<"生成二叉树的中序遍历结果为:"<<endl。
47. t.In_Order()。
48. cout<<endl。
49.50. t.DestroyTree()。
51.return 0。
52.}53.54.template<class Type>55.BinaryTree<int> HuffmanTree(Type f[],int n)56.{57.//生成单节点树58. Huffman<Type> *w = new Huffman<Type>[n+1]。
59. BinaryTree<int> z,zero。
60.61.for(int i=1。
i<=n。
i++)62. {63. z.MakeTree(i,zero,zero)。
64. w[i].weight = f[i]。
65. w[i].tree = z。
66. }67.68.//建优先队列69. MinHeap<Huffman<Type>> Q(n)。
70.for(int i=1。
i<=n。
i++) Q.Insert(w[i])。
71.72.//反复合并最小频率树73. Huffman<Type> x,y。
74.for(int i=1。
i<n。
i++)75. {76. x = Q.RemoveMin()。
77. y = Q.RemoveMin()。
78. z.MakeTree(0,x.tree,y.tree)。
79. x.weight += y.weight。
80. x.tree = z。
81. Q.Insert(x)。
82. }83.84. x = Q.RemoveMin()。
85.86.delete[] w。
87.88.return x.tree。
89.}(2)BinaryTree.h 二叉树实现[cpp]view plain copy1.#include<iostream>ing namespace std。
3.4.template<class T>5.struct BTNode6.{7. T data。
8. BTNode<T> *lChild,*rChild。
9.10. BTNode()11. {12. lChild=rChild=NULL。
13. }14.15. BTNode(const T &val,BTNode<T> *Childl=NULL,BTNode<T> *Childr=NULL)16. {17. data=val。
18. lChild=Childl。
19. rChild=Childr。
20. }21.22. BTNode<T>* CopyTree()23. {24. BTNode<T> *nl,*nr,*nn。
25.26.if(&data==NULL)27.return NULL。
28.29. nl=lChild->CopyTree()。
30. nr=rChild->CopyTree()。
31.32. nn=new BTNode<T>(data,nl,nr)。
33.return nn。
34. }35.}。
36.37.38.template<class T>39.class BinaryTree40.{41.public:42. BTNode<T> *root。
43. BinaryTree()。
44. ~BinaryTree()。
45.46.void Pre_Order()。
47.void In_Order()。
48.void Post_Order()。
49.50.int TreeHeight()const。
51.int TreeNodeCount()const。
52.53.void DestroyTree()。
54.void MakeTree(T pData,BinaryTree<T> leftTree,BinaryTree<T> rightTree)。
55.void Change(BTNode<T> *r)。
56.57.private:58.void Destroy(BTNode<T> *&r)。
59.void PreOrder(BTNode<T> *r)。
60.void InOrder(BTNode<T> *r)。
61.void PostOrder(BTNode<T> *r)。
62.63.int Height(const BTNode<T> *r)const。
64.int NodeCount(const BTNode<T> *r)const。
65.}。
66.67.template<class T>68.BinaryTree<T>::BinaryTree()69.{70. root=NULL。