贪心算法
来源:互联网 发布:人工智能李开复百度云 编辑:程序博客网 时间:2024/06/11 04:06
贪心算法和动态规划有异曲同工之秒,能用贪心法的,就不用动规。我只想说,动态规划真TM难。
- 分数背包问题
- Huffman编码
贪心算法这样一个算法,它在每一步都做出在当时看来是最优的方案,即它总是做出局部最优的选择。然后构成一个全局最优解。贪心算法也是一个强有力的算法,很多问题都能用贪心算法来解决。下面就说说两个经典的问题,分数背包问题和哈夫曼(Huffman)编码。
分数背包问题
分数背包问题是这样的,假设有一个背包体积为W, 有一系列商品,他们的体积各不相同,找出获取商品价值最大的方案。分数背包问题和0-1背包问题的惟一区别在于,是否可以取商品的一部分。贪心算法可以求解分数背包问题,但无法求解0-1背包问题。
正如贪心算法一样,人总是贪的,如果真有这样一件事,我们当然希望全部拿走,但是奈何空间有限。为简单起见,假设所有数据都是整数,该问题的输入和输出如下所述:
输入:
第一行是一个整数m,表示商品的数量
第二行是m个整数,表示商品的价值
输出:
最大价值v
定义p = v / w,我们称之为性价比,很明显,我们当然应该取性价比最高的商品,即价值最高,体积最小。
比如一个体积为50的背包,有三件商品,体积和价值分别为
商品(i) 1 2 3 体积(v) 10 20 30 价值(w) 60 100 120 性价比(p) 6 5 4所以我们先取商品1, 价值为60, 占10体积,还剩40体积,再取商品2,价值为100, 占20体积,还剩20体积,最后取20体积商品3,价值为4 * 20 = 80, 所以最优方案是60 + 100 + 80 = 240. 代码如下所示:
#include <stdio.h>#include <string.h>#include <stdlib.h>typedef struct Objcect { int c; int v; double p;}Object;int compare(const void* n1, const void* n2){ return ((struct Objcect*)n2)->p - ((struct Objcect*)n1)->p;}int main(void){ int m, w; while(scanf("%d", &m), m) { struct Objcect* objs = (struct Objcect*)malloc(sizeof(struct Objcect) * m); for(int i = 0; i != m; ++i) { scanf("%d %d", &objs[i].c, &objs[i].v); objs[i].p = (double)objs[i].v / objs[i].c; } qsort(objs, m, sizeof(struct Objcect), compare); scanf("%d", &w); double value = 0; for(int i = 0; i != m && w != 0; ++i) { if(objs[i].c <= w) { value += objs[i].v; w -= objs[i].c; }else { value += w * objs[i].p; w = 0; } } printf("%.2lf\n", value); free(objs); } return 0;}
输出结果如下:
Huffman编码
哈夫曼编码的实用性还挺高的,可以有效的压缩数据,哈夫曼编码是这样的,假设有一个文件,其中每个字符都有一个使用频率,可以给每个字符构造一个编码,达到压缩文件的目的,如下所示:
字符 a b c d e f 频率 45 13 12 16 9 5 变长编码 0 101 100 111 1101 1100哈夫曼编码就是一个贪心的过程,每次选择频率最小的两个数据,最终构成一棵哈夫曼树,过程如下:
初始状态,集合里是每个结点,首先选择「e」和「f」点,如下所示:
然后,构造一个结点,左右子树分别是这两个结点,结点的频率值为两者之和,如下所示:
重复上述过程,直至最终只有一个结点,如下所示:
从根结点开始,沿路径到根结点,左子树为0,右子树为1,即可得到各编码值。代码如下所示:
#include <stdio.h>#include <string.h>#include <stdlib.h>typedef struct Node { int freq; char letter; struct Node* leftChild; struct Node* rightChild;}Node;int nodeCompare(const void* node1, const void* node2){ return (*(struct Node**)node1)->freq - (*(struct Node**)node2)->freq;}struct Node* generateHuffman(int m){ struct Node* root = NULL; struct Node* List[m]; for(int i = 0; i != m; ++i) { struct Node* node = (struct Node*)malloc(sizeof(struct Node)); scanf("%s %d", &node->letter, &node->freq); node->leftChild = NULL; node->rightChild = NULL; List[i] = node; } //集合中共有多少个元素 int total = m; while(total > 1) { //按照频率高低进行排序 qsort(List, total, sizeof(struct Node*), nodeCompare); struct Node* node1 = (struct Node*)List[0]; struct Node* node2 = (struct Node*)List[1]; if(node1 && node2) { struct Node* node = (struct Node*)malloc(sizeof(struct Node)); node->letter = -1; node->freq = node1->freq + node2->freq; node->leftChild = node1; node->rightChild = node2; //删除第一、第二两个结点,其他元素前移 for(int i = 2; i != total; ++i) List[i - 2] = List[i]; //将最后一点加入到集合 List[total - 1] = NULL; List[total - 2] = node; --total; root = node; //修改根结点 } } return root;}//输出Huffman编码//code,用于记录当前结点的编码值void printHuffmanCode(struct Node* root, int* code){ //当前遍历结点索引 static int index = 0; if(root == NULL) return ; //如果是往左走,记录结点值为0 if(root->leftChild != NULL) { code[index++] = 0; printHuffmanCode(root->leftChild, code); } //如果是往右走,记录结点值为1 if(root->rightChild != NULL) { code[index++] = 1; printHuffmanCode(root->rightChild, code); } //如果是有效结点 if(root->letter != -1) { //输出当前字符 printf("%c : ", root->letter); //输出当前编码 for(int i = 0; i != index; ++i) printf("%d", code[i]); printf("\n"); } //指针回溯 code[--index] = -1; return ;}//递归删除Huffman树void destroyTree(struct Node* root){ if(root->leftChild != NULL) destroyTree(root->leftChild); if(root->rightChild != NULL) destroyTree((root->rightChild)); free(root); root = NULL; return ;}int main(void){ int m; while(scanf("%d", &m) == 1) { if(m == 0) break; //构建Huffman树 struct Node* root = generateHuffman(m); int* code = (int*)malloc(sizeof(int) * m); memset(code, -1, sizeof(int) * m); printHuffmanCode(root, code); destroyTree(root); free(code); } return 0;}
结果输出如下所示: