1. 贪心算法概览

贪心算法是一种算法思想。希望能够满足限制的情况下将期望值最大化。比如:Huffman编码,Dijkstra单源最短路径问题,Kruskal最小生成树 等问题都希望满足限制的情况下用最少的代价找到解决问题的办法。

这个办法就是贪心算法的思想。
实际用贪心算法解决问题可以有如下几步:

  1. 当看到这类问题时 我们能够联想到贪心算法:针对一组数据,我们定义了期望值和限制值,希望在满足限制值的情况下期望值最大。
    比如最短路径中,限制值是每一次移动只能向下或向右,期望值是走到终点(某个顶点),期望用最少的步数走到目标顶点。
  2. 尝试使用贪心算法来解决问题:每一次做出选择时在对限制值同等贡献量的情况下,选择对期望值贡献最大的数据。
  3. 尝试在案例数据中举例,查看贪心方式的实验结果是否最优。

贪心算法适用的案例 是 每一次的选择都是独立事件,不会受到之前选择的影响。比如有权图中的最短路径的查找,当前的选择会对后续的选择造成影响,第一次选择最优的,后续可选的权值都是特别大的,则期望值并不是最优的。

可以看看如下几个简单实践。

2. 贪心算法实践

2.1 分糖果

我们有m个糖果和n个孩子。我们现在要把糖果分给这些孩子吃,但是糖果少,孩子多(m<n),所以糖果只能分配给一部分孩子。

每个糖果的大小不等,这m个糖果的大小分别是s1,s2,s3,…,sm。除此之外,每个孩子对糖果大小的需求也是不一样的,只有糖果的大小大于等于孩子的对 糖果大小的需求的时候,孩子才得到满足。假设这n个孩子对糖果大小的需求分别是g1,g2,g3,…,gn。如何分配糖果,能尽可能满足最多数量的孩子?

期望值:最多满足孩子的个数
限制值:糖果大小s >= g ,才视为该糖果满足该孩子。
满足限制值的情况下,不论用小糖果还是大糖果满足孩子,对期望值的贡献都是一样的,满足的孩子个数++ 而已。

贪心算法:

  • 对于一个孩子,用较小的糖果能够满足,则没有必要用更大的糖果;更大的糖果可以用来满足更多需求的孩子。
  • 对于一个糖果,从较小需求的孩子开始,越容易满足。

代码如下:

// simple greedy algorithm: distribute candies
// six childs' request: 1 1 3 2 2 8
// five candies size:   4 2 5 7 9
//
// Greedy algorithm is suitable to solve the problem.
// We can let the smaller candy's size to satisfy the
// smaller resquest of child.
int distributeCandy(vector<int> candies,vector<int> childs) {if(candies.size() == 0 || childs.size() == 0) {return 0;}// sort, we can compare from small to big requestsort(candies.begin(), candies.end());sort(childs.begin(), childs.end());int res = 0;int i, j;for (i = 0, j = 0;i < childs.size() && j < candies.size(); i++,j++) {if (childs[i] <= candies[j]) {res ++;}}return res;
}

完整测试代码:
https://github.com/BaronStack/DATA_STRUCTURE/blob/master/greedy/distribute_candies.cc

2.2 钱币找零

这个问题在我们的日常生活中更加普遍。假设我们有1元、2元、5元、10元、20元、50元、100元这些面额的纸币,它们的张数分别是c1、c2、c5、c10、c20、c50、c100。我们现在要用这些钱来支付K元,最少要用多少张纸币呢?
在生活中,我们肯定是先用面值最大的来支付,如果不够,就继续用更小一点面值的,以此类推,最后剩下的用1元来补齐。

期望值:纸币数目最少
限制值:每种面额的张数

在满足限制值的情况下,希望用最少的纸币数目达成期望值。不论使用面额大的纸币还是面额小的纸币,期望值的纸币数目都会++,贡献一样。所以相同贡献值的情况下,使用更大面额的纸币更容易满足期望值。

代码如下:

int cmp(pair<int, int> a, pair<int, int> b) {return a.first > b.first;
}// Get the total num of papers that satisfy the K
// param1: nums of every coin
// param2: target number
// param3: cost of every coin in coins
void getCoinNums(vector<pair<int,int>> coins,int K, vector<pair<int,int>> &result) {if (coins.size() == 0) {return;}int i, tmp;// sort from biger to smallersort(coins.begin(), coins.end(), cmp);i = 0;tmp = 0;while(K && i < coins.size()) {tmp = K / coins[i].first;if (tmp != 0) {int real_nums;// defend the real coin nums overheadif (tmp <= coins[i].second) {real_nums = tmp;} else {real_nums = coins[i].second;}K -= real_nums * coins[i].first;result.push_back(make_pair(coins[i].first, real_nums));}i++;}
}

完整测试代码:
https://github.com/BaronStack/DATA_STRUCTURE/blob/master/greedy/coins_charge.cc

2.3 最多覆盖区间

假设我们有n个区间,区间的起始端点和结束端点分别是[l1, r1],[l2, r2],[l3, r3],…,[ln, rn]。我们从这n个区间中选出一部分区间,这部分区间满足两两不相 交(端点相交的情况不算相交),最多能选出多少个区间呢?

比如区间: [6,8], [2,4], [3,5], [1,5], [5,9], [8,10], 则最多不相交区间为 : [2,4], [6,8], [8,10]

我们将各个区间的右端点从小到大排序,每次选择区间时只需要确认当前区间的左端点比上一个区间的右端点大就可以了。之所以选择对右端点进行从小到大的排序,因为右端点决定的是一个区间的下界,我们每次选择尽可能选择下界小且和之前的区间没有相交的区间,则才能够得到最多的不相交区间。

实现代码如下:

int cmp(pair<int, int> a, pair<int, int> b) {if (a.second < b.second) {return 1;} else if (a.second == b.second &&a.first < b.first) {return 1;} else {return 0;}
}// algorithm:
// 1. Sort the array with right node increase
// 2. Maintain a num e, if rest of the interval's left node
// bigger than e, then the interval will be choosen
//
// example:
// Befor sort: [6,8], [2,4], [3,5], [1,5], [5,9], [8,10]
// After sort: [2,4], [1,5], [3,5], [6,8], [5,9], [8,10]
//
// result : [2,4], [6,8], [8,10]
void intervalCoverage(vector<pair<int,int>> intervals,vector<pair<int,int>> &result) {if (intervals.size() == 0) {return;}int i, e, count;sort(intervals.begin(), intervals.end(), cmp);e = -1;count = 0;for (i = 0;i < intervals.size(); i++) {if(intervals[i].first >= e) {count ++;e = intervals[i].second;result.push_back(make_pair(intervals[i].first,intervals[i].second));}}
}

完整测试代码:
https://github.com/BaronStack/DATA_STRUCTURE/blob/master/greedy/interval_coverage.cc

2.4 哈夫曼编解码

哈夫曼编码是一种针对数据高效压缩的编码方式,能够达到20%-90%的压缩比。
比如:

  • 1000长度的字符串 需要1000bytes = 8000 bit的存储空间。
  • 优化:如果1000长度的字符串中总共有8个不同的字符,则这八个字符可以用三个bit位就能表示(000-111),1000长度的字符串只需要3000 bit的存储空间
  • 进一步优化:haffman编码,每个字符的bit位表示可以不定长(解码会复杂一些),总长度不会超过3位(1000字符不会超过3000bit的存储空间)。haffman编码为了满足不定长的要求,不会出现针对某一个字符的编码是另一个字符编码前缀的情况。

比如如下字符表,huffman编码 以及 其对应的总二进制位数 ,最后1000字符总共也只有2100的bit存储空间。

所以这里根据频率构建huffman 编码的过程就用到了贪心的思想。

构建huffman树的过程 选择两个小频率的字符开始构建的父节点作为一个新节点,再选择一个次小的与该新节点一起构建一个新的父节点,依次由下向上构建。最终出现频率越高的字符串越靠近根节点。

构建过程如下:

void huffmanTree(priority_queue<Node> &q) {// root node is store in proiority_queue// when the q size is 1while (q.size() != 1) {Node *left = new Node(q.top()); q.pop();Node *right = new Node(q.top()); q.pop();// father's node and it's left and right childNode node('R', left->frequency + right->frequency, left, right);q.push(node);}
}

构建完huffman树之后,进行各个字符的编码,这里仅仅将所有的左子树权值设置为0,又子树权值设置为1就可以了

huffman编码过程如下:

// Huffman encode function
// param1: Root is the huffman tree's root node
// param2: prefix is the encode result per char
// param3: a map with 'char' and it's huffman's encode
void huffmanEncode(Node *root, string &prefix,map<char, string> &result) {string m_prefix = prefix;if (root->left == nullptr)return;// set the left weight recursionprefix += "0";if (root->left->left == nullptr) {// find the char's result in the leaf noderesult[root->left->c] = prefix;} else {huffmanEncode(root->left, prefix, result);}// back to the begin node to set the right weightprefix = m_prefix;prefix += "1";if (root->right->right == nullptr) {result[root->right->c] = prefix;} else {huffmanEncode(root->right, prefix, result);}
}

huffman解码如下:

// Huffman decode function
// param1: des is the input string to be decode
// param2: res is the map between char and huffman's
// encode string
// param3: decode string
bool huffmanDecode(string des, map <char, string> res,string &result) {if (des == "") {return false;}int i;map<char,string>::const_iterator it;string buf_str = "";for (i = 0; i < des.size(); i ++) {buf_str += des[i];for (it = res.begin() ; it != res.end(); it++ ) {if (it->second == buf_str) {result += it->first; buf_str = "";break;}}if(i == des.size() - 1 && it == res.end()) {return false;}}return true;
}

完整测试代码:
https://github.com/BaronStack/DATA_STRUCTURE/blob/master/greedy/haffman.cc

贪心算法简单实践 -- 分糖果、钱币找零、最多区间覆盖、哈夫曼编解码相关推荐

  1. 贪心算法解决(分糖果问题)

    思路:(1)先进行分析,找到符合一边的情况,再找符合另一边的情况. (2)我们先确定右边评分大于左边的情况,即从前向后遍历. 此时局部最优,只要右边孩子的评分大于左边,右边孩子比左边孩子多一个糖果.全 ...

  2. 钱币找零问题---贪心算法入门例题

    2019独角兽企业重金招聘Python工程师标准>>> 笔者近期无意中发现了一篇关于贪心算法的博客,写的真好.作为一名算法萌新,感觉这篇文章来入门真心不错,于是摘记其中一个典型的关于 ...

  3. 贪心:assign cookies分糖果

    贪心算法的核心: 遵循某种规律,使用最少的资源来完成目标 所以在了解贪心算法的时候需要明确两点 寻找共有的规律 每一步的迭代使用最优的策略(消耗最少的资源) 问题如下: 已知一些孩子和一些糖果,每个孩 ...

  4. 埃及分数C语言算法,2019-02-27 埃及分数 (贪心算法简单示例)

    1 . 问题 埃及分数是指分子是1的分数,也叫单位分数.古代埃及人在进行分数运算时.只使用分子是1的分数.因此这种分数也叫做埃及分数,或者叫单分子分数. [Baidu百科] 给定一个分数,如7/8,我 ...

  5. 数据结构与算法(python)递归:找零问题

    参考自 MOOC数据结构与算法Python版 目录 一.什么是递归Recursion 1. 初识递归 1.1 数列求和 1.2 递归"三定律" 2. 递归的应用 2.1 任意进制转 ...

  6. 算法导论习题解答 16-1 (找零问题)

    考虑用最少的硬币找nnn美分零钱的问题.假定每种硬币的面额都是整数. a. 设计贪心算法求解找零问题,假定有25美分.10美分.5美分和1美分4中面额的硬币.证明你的算法能找到最优解. b. 假定硬币 ...

  7. LeetCode简单题之柠檬水找零

    题目 在柠檬水摊上,每一杯柠檬水的售价为 5 美元.顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯. 每位顾客只买一杯柠檬水,然后向你付 5 美元.10 美元或 20 美元.你必 ...

  8. 【id:180】【20分】D. DS二叉树--赫夫曼树解码(不含代码框架)

    题目描述 已知赫夫曼编码算法和程序,在此基础上进行赫夫曼解码 在赫夫曼树的类定义中增加了一个公有方法: int  Decode(const string codestr, char txtstr[]) ...

  9. 钱币找零问题 - 动态规划

    先从一个题目引出动态规划. 有数组 penny,penny 中所有的值都为正数且不重复.每个值代表一种面值的货币,每种面值的货币可以使用任意张,给定一个整数 N 表示货币总数,再给定一个整数 aim ...

最新文章

  1. 浅谈 Java Printing
  2. git fetch 和git pull 的差别
  3. ubuntu20.04 黑屏 进入不了图形界面 dev/sda1: clean
  4. bzoj 4880 [Lydsy1705月赛]排名的战争 贪心
  5. explain for connection用法
  6. 关闭ArcGIS9.3时 .NET Framework出现尝试读取或写入受保护的内存问题
  7. sumif三个条件怎么填_玩转excel求和函数,sumif/sumifs常用案例讲解
  8. Python学习笔记三(文件操作、函数)
  9. 为什么佛系青蛙住进了阿里淘宝——访《旅行青蛙》制作人
  10. ant a-table 表格数据同步
  11. Srm32 dwm1000 tdoa定位源码
  12. 企业微信 发送或接收 图片失败,怎么办?
  13. 深入探讨MFC消息循环和消息泵
  14. 基于android平台的感光设计,基于Android手机摄像头的可见光通信技术研究
  15. 选取域名需要注意的几个方面
  16. 刘彬20000词汇02
  17. VMware安装2003,win7,2008,kali
  18. [跟练]基于七普修正Worldpop人口栅格数据(附2020年worldpop100m人口栅格)
  19. 使用outlook 2007配置microsoft exchange邮箱方法步骤
  20. 《图书管理系统——“借书还书”模块》

热门文章

  1. Myeclise下tomcat启动报错,启动超时
  2. 与jQuery的感情碰撞——由浅入深学jQuery
  3. Ruby 之 Block, Proc, Lambda 联系--区别,转载
  4. 句法依存分析_[NLP学习笔记]句法分析
  5. 基于FCN,U-Net的深度学习医学影像分割算法(细胞分割算法)以及传统算法分析
  6. 字符串插入_计算机毕业设计中小程序插入html代码
  7. idea java jni 调试_IntelliJ IDEA平台下JNI编程(一)—HelloWorld篇
  8. 无法使用_解决kali linux 2020 安装完后发现无法使用 ifconfig
  9. 华为鸿蒙4月推送名单,华为鸿蒙升级名单已确认,首批将有44款机型升级,其中有你吗?...
  10. OpenCV制作自己的线性滤镜