【算法导论】第16章贪心算法
1. 算法描述
适用于最优化问题的算法往往包含一系列步骤,每一步都有一组选择,对许多最优化问题来说,采用动态规划方法来决定最佳选择有点繁琐了,只要采用另一些更简单有效的算法就行了。贪心算法是使所做的选择看起来都是当前最佳的,期望通过所做的局部最优选择来产生衣蛾全局最优解。
贪心算法是一种很有效的方法,适用于一大类问题。之后要讨论的许多都可以用贪心算法解决,比如赫夫曼树、最小生成树、地杰斯特拉的单元最短路径等。贪心算法是通过做一系列的选择来给出某一问题的最优解,一般开发一个贪心算法时,所遵循的过程比一般情况更复杂些。有如下步骤:
(1)决定问题的最优子结构
(2)设计出一个递归解
(3)证明在递归的任一阶段,最优选择之一总是贪心选择。那么,做贪心选择总是安全的
(4)证明通过做贪心选择,所有的子问题(除一个以外)都为空
(5)设计出一个实现贪心策略的递归算法
(6)将递归算法转化为迭代算法
2. 问题求解
2.1 赫夫曼编码与解码
赫夫曼编码是一种被广泛应用而且非常有效的数据压缩技术,赫夫曼贪心算法使用了一张字符出现频度表,根据它来构造一种将每个字符表示成二进制串的最优方式,这里编码方案中药提到一种前缀编码:即在编码方案中,没有一个编码是另一个编码的前缀,这样的编码称为前缀编码。解码过程也需要有一种关于前缀编码的方便表示,使得初始编码可以很容易的被识别出来。
2.1.1 赫夫曼编码算法
设C是一个包含n个字符的集合,且每个字符c都是一个出现频度为f[c]的对象,算法以自底向上的方式构造出最优编码所对应的树T,它从|C|个叶结点的集合开始,并依次执行|C|-1次“合并”操作来构造最终的树。Q是一个以f为关键字的最小优先级队列,用来识别出要合并的两个频度最低的对象。两个对象合并的结果是一个新对象,其频度为被合并的两个对象的频度之和。
具体算法如下:
1 huffman(C) 2 n=|C| 3 Q=C 4 for i=1 to n-1 5 do 构建一个新节点z 6 left[z]=x=extractMin(Q) 7 right[z]=y=extractMin(Q) 8 f[z]=f[x]+f[y] 9 INSERT(Q,z) 10 return root of the tree
2.1.2 赫夫曼解码算法
1 void huffmanEncode(htNode *ht,char *hv,char vchar[]) 2 3 int len=strlen(vchar); 4 for(i=0;i<=len;) 5 { 6 if(ht[m].rchild==0 && ht[m].lchild==0)//为叶子节点 7 { 8 hv[j++]=ht[m].value; 9 m=2*n-1; 10 } 11 else 12 { 13 if(vchar[i]=='0')//为0则取左子树 14 { 15 m=ht[m].lchild; 16 i++; 17 } 18 else //为1则取右子树 19 { 20 m=ht[m].rchild; 21 i++; 22 } 23 } 24 }
2.1.3 赫夫曼编码与解码的实现代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /*------------------------------------------------------------------- 2 *名称:赫夫曼算法的实现 3 *作者:lp 4 *时间:2012.6.28 5 *环境:vc++6.0 6 *实现描述: 7 * 编码过程:; 8 * 解码过程:; 9 *-------------------------------------------------------------------*/ 10 #include<stdio.h> 11 #include<malloc.h> 12 #include<string.h> 13 #define en 100 //可以解码的字符串最长字符个数 14 #define n 6//出现的字符个数 15 typedef struct{ 16 unsigned int weight;//value字符出现的次数 17 unsigned int parent,lchild,rchild; 18 char value;//字符值 19 int flag;//以后要用到的标记 20 }htNode; 21 /*--------------求最小值---------------------------------------------*/ 22 int selectMin(htNode*ht) 23 { 24 int i,k,m=2*n-1; 25 for(i=1;i<=m;i++) 26 if(ht[i].flag==0) 27 { 28 k=i; 29 break; 30 } 31 for( i=2;i<=m;i++) 32 { 33 //ht[i].weight!=0 && ht[i].parent==0用于有区分的选择最小值节点 34 if((ht[i].weight!=0) &&(ht[i].parent==0)&& (ht[i].flag==0)&&(ht[i].weight<ht[k].weight)) 35 k=i; 36 } 37 ht[k].flag=1; 38 return(k); 39 } 40 /*-------建立赫夫曼树,并给出每个字符的编码----------------------*/ 41 void huffmanCode(htNode*ht,char **hc,char wchar[],int w[]) 42 { 43 htNode *p=ht; 44 int i,j,m,t1,t2; 45 m=2*n-1; 46 for(i=1;i<=n;i++)//叶子节点的初始化 47 { 48 p[i].weight=w[i-1]; 49 p[i].parent=0; 50 p[i].lchild=0; 51 p[i].rchild=0; 52 p[i].value=wchar[i-1]; 53 p[i].flag=0; 54 } 55 for(i=n+1;i<=m;i++)//中间节点的初始化 56 { 57 p[i].weight=0; 58 p[i].parent=0; 59 p[i].lchild=0; 60 p[i].rchild=0; 61 p[i].value=0; 62 p[i].flag=0; 63 } 64 for(i=n+1;i<=m;i++)//建立huffman树 65 { 66 t1=selectMin(ht);//得到最小值t1 67 t2=selectMin(ht);//得到最小值t2 68 ht[t1].parent=i; 69 ht[t2].parent=i; 70 ht[i].lchild=t1; 71 ht[i].rchild=t2; 72 ht[i].weight=ht[t1].weight+ht[t2].weight; 73 } 74 /*----从叶子节点到根逆向求每个字符的赫夫曼编码----*/ 75 unsigned int k,start; 76 char *cd=(char *)malloc(sizeof(char)*n); 77 for(i=1;i<=n;i++)//逐个字符求赫夫曼编码 78 { 79 80 start=n-1;//编码结束位置 81 cd[n-1]='\0';//编码结束符 82 k=i; 83 for(j=ht[k].parent;j!=0;j=ht[k].parent)// 84 { 85 if(ht[j].lchild==k) cd[--start]='0'; 86 else cd[--start]='1'; 87 k=j; 88 } 89 strcpy(hc[i],&cd[start]);//从cd复制编码到ht 90 } 91 free(cd); 92 } 93 /*--------------------解码过程--------------------------------*/ 94 void huffmanEncode(htNode *ht,char *hv,char vchar[]) 95 { 96 int i,j=0,m=2*n-1; 97 int len=strlen(vchar); 98 for(i=0;i<=len;) 99 { 100 if(ht[m].rchild==0 && ht[m].lchild==0)//为叶子节点 101 { 102 hv[j++]=ht[m].value; 103 m=2*n-1; 104 } 105 else 106 { 107 if(vchar[i]=='0')//为0则取左子树 108 { 109 m=ht[m].lchild; 110 i++; 111 } 112 else //为1则取右子树 113 { 114 m=ht[m].rchild; 115 i++; 116 } 117 } 118 } 119 hv[j]='\0';//结束符 120 } 121 122 void main() 123 { 124 //wchar[]表示出现的各个字符, 125 //w[]表示各个字符出现的次数, 126 int i; 127 /*----------------构建赫夫曼编码--------------------*/ 128 char wchar[]="abcdef"; 129 int w[]={45,13,12,16,9,5}; 130 int m=2*n-1;//huffman树中的节点个数。 131 htNode*ht=(htNode*)malloc(sizeof(htNode)*(m+1));//分配m+1个节点空间,0号单元未使用 132 char **hc=(char **)malloc(sizeof(char*)*(n+1)); 133 for(i=1;i<=n;i++) 134 hc[i]=(char *)malloc(sizeof(char)*n); 135 huffmanCode(ht,hc,wchar,w); 136 printf("每个字符的相应赫夫曼编码为:\n"); 137 for(i=1;i<=n;i++) 138 printf("%c --> %s \n",wchar[i-1],hc[i]); 139 140 /*------用已经构建的赫夫曼编码进行01编码的解码------*/ 141 int k=0; 142 char vchar[]="101010011111001101";//待解码的01编码 143 char *hv=(char *)malloc(sizeof(char)*en); 144 huffmanEncode(ht,hv,vchar); 145 printf("\n编码%s 的解码结果为:\n%s\n",vchar,hv); 146 } 147 148 149 150 151
3. 参考资料:
(1)算法导论
(2)数据结构
(3)http://hi.baidu.com/justtheend/item/5f2ddaf2008bfa1ece9f3286
(4)http://www.cnblogs.com/Jezze/archive/2011/12/23/2299884.html