学习要点:
1、概念
2、基本要素(+与动态规划算法的差异)
3、应用范例:
(1)活动安排问题
(2)最优装载问题
(3)哈夫曼编码
(4)最优服务次序问题
(5)最小生成树
(6)最大边权值最小的生成树
(7).......
1、概念
贪心算法总是作出当前看来是最好的选择。
也就是说贪心算法并不从整体最优上加以考虑,它所作出的选择只是在某种意义上的局部最优选择。
在一些情况下,即使贪心算法不能得到整体最优解,但其最终结果却是最优解的很好的近似解。
2、基本要素
贪心选择:通过一系列的选择得到问题的解,且所作的每一个选择都是当前状态下局部最好选择。
两个重要性质:贪心选择性质 + 最优子结构性质
(1)贪心选择性质———————————————与动态规划算法的主要区别
贪心选择性质:所求问题的整体最优解可以通过一系列局部最优的选择(即贪心选择)来达到。
注:与动态规划的区别
共同点:都要求问题具有最优子结构性质
在动态规划中,每步所做的选择往往依赖于相关子问题的解,所以只有在解出相关子问题后才能作出选择。通常以自底向上的方式解各子问题。
而在贪心算法中,仅在当前状态下做出最好的选择,即局部最优选择,然后再去解作出这个选择后产生的相应的子问题。所做的贪心选择可以依赖于以往所做过的选择,但不依赖于将来所做的选择,也不依赖于子问题的解。通常以自顶向下的方式进行,以迭代的方式做出相继的贪心选择,每做一次贪心选择就将所求问题简化为规模更小的子问题。
证:确定某个具体的问题是否具有贪心选择性质,必须证明每一步所做的贪心选择最终导致问题的整体最优解。
首先考察问题的一个整体最优解,并证明可修改这个最优解,使其以贪心选择开始,做了贪心选择后,原问题简化为规模更小的类似子问题。然后用数学归纳法证明,通过每一步做贪心选择,最终可得到问题的整体最优解。
(2)最优子结构性质
当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。
问题的最优子结构性质是该问题可用动态规划算法或贪心算法求解的关键特征。
3、应用范例
(1)活动安排问题
/*
*问题:要求高效地安排一系列争用某一公共资源的活动
*描述:设有n 个活动的集合E={1,2,...,n},其中每个活动都要求使用同一个资源,如演讲会场等,而在同一时间内只有一个活动可以使用这一资源。
每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si<fi。如果选择了活动i,则它在半开时间区间[si,fi)内占用资源。
若区间[si,fi)与区间[sj,fj)不相交,则称活动i和活动j是相容的。也就是说,当si>=fj或sj>=fi时,活动i与活动j相容。
*分析:活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合。
考虑到每个活动的开始、结束时间已定,要选择最佳活动安排,可以把各活动的起始时间和结束时间分别存储于数组s和f中,
且按照结束时间非递减排序,最早结束活动先被安排,依次贪心选择活动进行安排。
该贪心选择的意义是使剩余的可安排时间段极大化,以便安排尽可能多的相容活动。
*B Y :xymaqingxiang
2014-04-19
*/

1 #include <stdio.h> 2 #include <stdlib.h> 3 4 #define MAX 20 5 #define INFINITY 32768 6 7 struct In 8 { 9 int s; 10 int f; 11 }t[MAX] ; 12 13 //按照f的值从小到大将结构体排序,关于结构体内的排序关键数据f的类型可以很多种,参考上面的例子写 14 int cmp( const void *a ,const void *b) 15 { 16 return (*(In *)a).f - (*(In *)b).f ; 17 } 18 19 void GreedySelector(int n, struct In t[], int a[]) 20 { 21 int i, j, count; 22 a[1] = 1; 23 j = 1; 24 count = 1; 25 for(i = 2; i < n+1; i++) 26 { 27 if(t[i].s >= t[j].f) 28 { 29 a[i] = 1; 30 j=i; 31 count++; 32 } 33 else 34 a[i] = 0; 35 } 36 } 37 38 //排序 39 void sort(int n, struct In t[]) 40 { 41 int i,j; 42 int t1,t2; 43 for(i=1;i<n+1;i++) 44 { 45 for(j=1;j<n+1;j++) 46 if(t[j].f>t[j+1].f) 47 { 48 t1=t[j].f; 49 t2=t[j].s; 50 51 t[j].f=t[j+1].f; 52 t[j].s=t[j+1].s; 53 54 t[j+1].f=t1; 55 t[j+1].s=t2; 56 } 57 } 58 } 59 60 int main() 61 { 62 int i = 0, j = 0; //控制程序流程 63 int n; //n:活动数目 64 int a[MAX] = {0}; //标记活动是否被安排:1--安排,0--未安排 65 66 for(i = 0; i < MAX; i++) 67 { 68 t[i].f = INFINITY; 69 t[i].s = 0; 70 } 71 72 printf("请输入活动数目(n):"); 73 scanf("%d", &n); //输入活动 74 printf(" 请输入各个活动的开始时间s和结束时间f: "); 75 for(i = 1; i < n+1; i++) //依次输入各个顾客服务时间 76 { 77 printf("第%d个活动的起始时间和结束时间: ",i); 78 scanf("%d", &t[i].s); 79 scanf("%d", &t[i].f); 80 } 81 82 printf(" "); 83 for(i = 1; i < n+1; i++) //依次打印各个活动的结束时间 84 printf("%d ",t[i].f); 85 printf(" "); 86 for(i = 1; i < n+1; i++) //依次打印各个活动的起始时间 87 printf("%d ",t[i].s); 88 89 sort(n,t); 90 91 /* qsort(t,n,sizeof(t[0]),cmp); //排序函数--快排思想 92 */ 93 printf(" "); 94 for(i = 1; i < n+1; i++) //依次打印各个活动的结束时间 95 printf("%d ",t[i].f); 96 printf(" "); 97 for(i = 1; i < n+1; i++) //依次打印各个活动的起始时间 98 printf("%d ",t[i].s); 99 100 GreedySelector(n,t,a); 101 102 printf(" "); 103 104 for(i = 1; i < n+1; i++) //打印活动标记数组 105 printf("%d",a[i]); 106 107 printf(" 被安排的活动有:"); 108 for(i = 1; i < n+1; i++) 109 if(a[i]==1) 110 printf("%d ",i); 111 112 printf(" "); 113 114 flushall(); 115 printf("按任意键继续..."); 116 getchar(); 117 return 0; 118 }
(2)最优装载问题
/*
*问题:要求在体积不受限制的情况下,将尽可能多的集装箱装上轮船
*描述:有一批集装箱要装上一搜载重量为c的轮船,其中集装箱i的重量为wi,选择尽可能多的集装箱
*分析:最优装载问题就是要在所给的这批集装箱中选择尽可能多的集装箱装上轮船,使其个数达到最大。
考虑到每个集装箱的重量已定,要把尽可能多的集装箱装上轮船,可以把各个集装箱的重量存储在数组w中,
且按照重量wi从小到大排序,最轻的先被选中,依次贪心选择集装箱进行装载。
该贪心选择的意义是使剩余的可利用空间极大化,以便装载尽可能多的集装箱。
*B Y :xymaqingxiang
2014-04-19
*/

1 #include <stdio.h> 2 #include <stdlib.h> 3 4 #define MAX 20 5 #define INFINITY 32768 6 7 //按照w的值从小到大排序 8 int cmp(const void *a, const void *b) 9 { 10 return (*(int *)a - *(int *)b); 11 } 12 13 14 void load(int c, int n, int w[], int a[]) 15 { 16 int i; 17 for(i = 1; i < n+1 && w[i] <= c; i++) 18 { 19 a[i] = 1; 20 c -= w[i]; 21 } 22 } 23 24 //排序 --从小到大 25 /*void sort(int n, struct In t[]) 26 { 27 int i,j; 28 int t1,t2; 29 for(i=1;i<n+1;i++) 30 { 31 for(j=1;j<n+1;j++) 32 if(t[j].f>t[j+1].f) 33 { 34 t1=t[j].f; 35 t2=t[j].s; 36 37 t[j].f=t[j+1].f; 38 t[j].s=t[j+1].s; 39 40 t[j+1].f=t1; 41 t[j+1].s=t2; 42 } 43 } 44 } 45 */ 46 47 int main() 48 { 49 int i = 0, j = 0; //控制程序流程 50 int n,c; //n:集装箱数目 51 int w[MAX] = {0}; 52 int a[MAX] = {0}; //标记集装箱是否被装载:1--被装载,0--未被装载 53 54 printf("请输入轮船载重(c):"); 55 scanf("%d", &c); //输入集装箱数目n 56 printf("请输入集装箱数目(n):"); 57 scanf("%d", &n); //输入集装箱数目n 58 printf(" 请输入各个集装箱的重量w[i]: "); 59 for(i = 1; i < n+1; i++) //依次输入各个集装箱重量w 60 { 61 printf("第%d个集装箱的重量w: ",i); 62 scanf("%d", &w[i]); 63 } 64 65 printf(" "); 66 for(i = 1; i < n+1; i++) //依次打印各个集装箱重量w 67 printf("%d ",w[i]); 68 69 // sort(n,t); 70 71 qsort(w,n,sizeof(w[0]),cmp); //排序函数--快排思想 72 73 printf(" "); 74 for(i = 1; i < n+1; i++) //依次打印排序后各个集装箱重量w 75 printf("%d ",w[i]); 76 77 load(c,n,w,a); 78 79 printf(" "); 80 81 for(i = 1; i < n+1; i++) //打印活动标记数组 82 printf("%d",a[i]); 83 84 printf(" 被装载的集装箱有:"); 85 for(i = 1; i < n+1; i++) 86 if(a[i]==1) 87 printf("%d ",i); 88 89 printf(" "); 90 91 flushall(); 92 printf("按任意键继续..."); 93 getchar(); 94 return 0; 95 }
(3)哈夫曼编码
/*
*问题:基础的哈夫曼树的实现
*描述:已知每个字符出现的频率,构造哈夫曼树,并设计哈弗曼编码。
*要求:从终端读入字符集大小n、n个字符和n个权值,建立哈夫曼树,打印每个字符对应的哈夫曼编码,对终端输入的字符集解码,并显示编码结果。
哈夫曼树是由n个带权叶子结点构成的所有二叉树中带权路径长度最短的二叉树,又叫最优二叉树。
树的带权路径长度(WPL)为树中从根到所有叶子结点的各个带权路径长度之和。
带权路径长度为从树根到某一结点的路径长度(从一个结点到另一个结点所经过的分支数目)与该结点的权的乘积。
*分析:算法步骤
1、初始化:用给定的n个权值{w1,w2,....wn}对应由n棵二叉树构成的森林F={T1,T2,...,Tn},其中每一棵二叉树Ti都只有一个权值为wi的根结点,其左右子树为空。
2、找最小树:在森林F中选择两棵根结点权值最小的二叉树,作为一棵新二叉树的左、右子树,标记新二叉树的根结点权值为其左、右子树的根结点权值之和。
3、删除和加入:从F中删除被选中的那两棵二叉树,同时把新构成的二叉树加入到森林F中。
4、判断:重复2、3操作,知道森林中只含有一棵二叉树为止,此时得到的这棵二叉树就是哈夫曼树。
*B Y :xymaqingxiang
2014-04-16
*/

1 #include<stdio.h> 2 #include<malloc.h> 3 #include<stdlib.h> 4 #include<string.h> 5 6 #define MAX 20 7 #define N 20 //叶子结点的最大值 8 #define M 2*N-1 //所有结点的最大值 9 10 typedef struct 11 { 12 char data; 13 int weight; //结点的权值 14 int parent; //双亲的下标 15 int lchild; //左孩子结点的下标 16 int rchild; //右孩子结点的下标 17 }HTNode,HuffmanTree[M+1]; //HuffmanTree是结构数组类型,0号单元不用 18 19 //typedef char * HuffmanCode[N+1]; //存储哈夫曼编码串的头指针的数组 20 typedef struct 21 { 22 char data; 23 char cd[M]; 24 }CNode; 25 typedef CNode * HuffmanCode[N+1]; 26 27 28 //在ht[1]~ht[i-1]的范围内选择两个parent为0且weight最小的结点,并将其序号分别赋值给是s1,s2返回 29 void Select(HuffmanTree ht,int pos,int *s1,int *s2) 30 { 31 int m1,m2,i; 32 m1=m2=32767; //给m1、m2赋最大值 33 for(i=1;i<=pos;i++) //在ht[1]~ht[i-1]的范围内选择 34 { 35 if(ht[i].parent==0&&ht[i].weight<m1) 36 { 37 m2=m1; 38 m1=ht[i].weight; 39 *s2=*s1; 40 *s1=i; 41 } 42 else if(ht[i].parent==0&&ht[i].weight<m2) 43 { 44 m2=ht[i].weight; 45 *s2=i; 46 } 47 } 48 if(ht[*s1].weight>=ht[*s2].weight) 49 { 50 i=*s1; 51 *s1=*s2; 52 *s2=i; 53 } 54 55 /*if(*s1>*s2) 56 { 57 i=*s1; 58 *s1=*s2; 59 *s2=i; 60 }*/ 61 } 62 63 //创建哈夫曼树ht[M+1],w[]存放n个权值 64 void CrtHuffmanTree(HuffmanTree ht,int w[],char d[], int n) 65 { 66 int s1,s2; 67 int i,m; 68 for(i=1;i<=n;i++) //初始化ht[]----1~n号单元存放叶子结点 69 { 70 ht[i].data=d[i]; 71 ht[i].weight=w[i]; 72 ht[i].parent=0; 73 ht[i].lchild=0; 74 ht[i].rchild=0; 75 } 76 m=2*n-1; //所有结点数目 77 for(i=n+1;i<=m;i++) //初始化ht[]----n+1~m号单元存放非叶子结点 78 { 79 ht[i].weight=0; 80 ht[i].parent=0; 81 ht[i].lchild=0; 82 ht[i].rchild=0; 83 } 84 s1=s2=1; 85 for(i=n+1;i<=m;i++) //创建非叶结点,建哈夫曼树 86 { 87 Select(ht,i-1,&s1,&s2); //在ht[1]~ht[i-1]的范围内选择两个parent为0且weight最小的结点,并将其序号分别赋值给是s1,s2返回 88 ht[i].weight=ht[s1].weight+ht[s2].weight; //标记权值、父子关系 89 ht[s1].parent=i; 90 ht[s2].parent=i; 91 ht[i].lchild=s1; 92 ht[i].rchild=s2; 93 } 94 } 95 96 //从叶子结点到根,逆向求每个叶子结点对应的哈夫曼编码 97 void CrtHuffmanCode(HuffmanTree ht,HuffmanCode hc,int n) 98 { 99 char *cd; 100 int c,i,p,start; 101 cd=(char *)malloc((n+1)*sizeof(char)); //分配当前编码的工作空间 102 cd[n-1]='