趣味算法(第二版)读书笔记: day1: 序章|学习的方法和目标. day2:算法之美|打开算法之门与算法复杂性 day3.算法之美|指数型函数对算法的影响实际应用 day4.数学之美|斐波那契数列与黄金分割 day5.算法基础|贪心算法基础 day6.算法基础||哈夫曼树 day7.算法基础||堆栈和队列 day8.算法基础||动态规划 day9.算法基础|分治策略 后续补充完善
作为以解决实际问题为导向的一名产品,其实现实中的很多问题都是复杂的,其底层原理是系统论,数学函数符合多元复变函数的应用,贪心算法只适用于单一场景,一元线性规划求解,后续在动态规划中将贪心算法与动态规划的对比以及系统论做一个整理和阐述:
个人建议在做读书笔记的时候,和动态规划一起通读,然后对比着学,食用效果更佳。
1.什么是贪心算法
2. 贪心算法的基本思路
3. 贪心算法在实际中的应用
4. 简单贪心算法举例
下面用一张图对书中课程做一个概括:


所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的仅仅是在某种意义上的局部最优解。 贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性(即某个状态以后的过程不会影响以前的状态,只与当前状态有关。) 所以,对所采用的贪心策略一定要仔细分析其是否满足无后效性。

建立数学模型来描述问题 把求解的问题分成若干个子问题 对每个子问题求解,得到子问题的局部最优解 把子问题的解局部最优解合成原来问题的一个解


我们日常使用汽车导航,首先你要选择应用场景,计算对应最优路线其算法的核心就是贪心算法的变种,根据你的选择,简化问题,比如:用时最短,路线最短,和费用最低。

例如常见的Dijkstra 算法,是由荷兰计算机科学家 Edsger Wybe Dijkstra 在1956年发现的算法,戴克斯特拉算法使用类似广度优先搜索的方法解决赋权图的单源最短路径问题。Dijkstra 算法原始版本仅适用于找到两个顶点之间的最短路径,后来更常见的变体固定了一个顶点作为源结点然后找到该顶点到图中所有其它结点的最短路径,产生一个最短路径树。本算法每次取出未访问结点中距离最小的,用该结点更新其他结点的距离。需要注意的是绝大多数的Dijkstra 算法不能有效处理带有负权边的图。

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例 1:
输入: g = [1,2,3], s = [1,1] 输出: 1 解释: 你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。 虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 所以你应该输出1。
示例 2: 输入: g = [1,2], s = [1,2,3] 输出: 2 解释: 你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。 你拥有的饼干数量和尺寸都足以让所有孩子满足。 所以你应该输出2. 提示:
「贪心算法」的直觉 1:
如果最小的饼干都不能满足胃口最小的小朋友,那么这块最小的饼干一定也不能满足比他(她)还贪心的小朋友。此时我们舍弃这块饼干。
因此当前问题贪心的点是:如果一个小朋友的胃口大小是 a ,我们在分配饼干的时候,给能他(她)大小为 a 的饼干,绝对不会给大小为 a + 1 的饼干,因此「贪心算法」应用在这个问题里,是一种「吝啬」的策略。
public class Solution {
public int findContentChildren(int[] g, int[] s) {
int gLen = g.length;
int sLen = s.length;
if (sLen == 0) {
return 0;
}
Arrays.sort(g);
Arrays.sort(s);
int gIndex = 0;
int sIndex = 0;
while (gIndex < gLen && sIndex < sLen) {
// 用最小的饼干去满足贪心程度最低的小朋友
if (g[gIndex] <= s[sIndex]) {
gIndex++;
sIndex++;
} else {
sIndex++;
}
}
return gIndex;
}
}复杂度分析:
时间复杂度:O(|g| \log |g| + |s| \log |s|)O(∣g∣log∣g∣+∣s∣log∣s∣),其中 |g|∣g∣ 和 |s|∣s∣ 分别是数组 g 和 s 的长度; 空间复杂度:O(\log |g| + \log |s|)O(log∣g∣+log∣s∣)。 「直觉 1」的「吝啬」的策略相比,我们还可以想出一种「大方」的策略。
「贪心算法」的直觉 2:
给最贪心的小朋友最大的饼干。如果最大的这块饼干都不能满足最贪心的小朋友,此时我们需要放弃最贪心的小朋友,进而考虑次贪心的小朋友。
参考代码 2:
public class Solution {
public int findContentChildren(int[] g, int[] s) {
int gLen = g.length;
int sLen = s.length;
if (sLen == 0) {
return 0;
}
Arrays.sort(g);
Arrays.sort(s);
int gIndex = gLen - 1;
int sIndex = sLen - 1;
int res = 0;
while (gIndex >= 0 && sIndex >= 0) {
if (s[sIndex] >= g[gIndex]) {
sIndex--;
gIndex--;
res++;
} else {
gIndex--;
}
}
return res;
}算法是手段,解决问题才是目的。 算法能解决的问题的多寡和被解决问题自身的重要性,决定了算法的重要性。 尤其是作为一名产品岗,将现实问题,拆解成为若干个子问题,并转化为数学问题建模求解,将运用到大量的运筹学和算法知识,因此需要加强学习,不断实践和迭代。
下一章:day6.算法基础||哈夫曼树