算法图解 Grokking Algorithms
一本非常基础的算法书,补补脑子。
一、大O表示法
- 算法的运行时间以不同的速度增加
- 大O表示法指出了最糟情况下的运行时间
- 一些常见的大O运行时间
- O(log n) 对数时间,比如二分查找
- O(n) 线性时间,比如简单查找
- O(n * logn) 快速排序
- O(n^2) 选择排序
- O(n!) 非常慢
- 大O中省略常数
Summary 1.
- 二分查找的速度比简单查找快得多
- O(log n) 比 O(n) 快
- 算法运行时间并不以秒为单位
- 算法运行时间是从其增速的角度度量的
- 算法运行时间用大O表示法表示
二、选择排序
- 内存:每个抽屉都有地址,存入的东西都记录地址
- 数组和链表
- 数组:读取快
- 链表:插入、删除快
- 选择排序 O(n^2) 每次拿出剩余的最大/小项,共进行 n * n 次
Summary 2.
- 计算机内容犹如一大堆抽屉
- 需要存储多个元素时,可使用数组或链表
- 数组的元素都在一起
- 链表的元素时分开的,其中每个元素都存储了下一个元素的地址
- 数组的读取速度很快
- 链表的插入和删除速度很快
- 在同一个数组中,所有元素的类型都必须相同
三、递归
- 递归指的是调用自己的函数
- 每个递归函数都有两个条件:基本条件和递归条件
- 栈有两种操作:压入和弹出
- 所有函数调用都进入调用栈
- 调用栈可能很长,这将占用大量的内存
四、快速排序
D&C算法是递归的。
- 找出基线条件,这种条件必须尽可能简单。
- 不断将问题分解(或者说缩小规模),直到符合基线条件。
复杂度 O(n * logn) (同归并排序)
欧几里得算法
又称辗转相除法
,是指用于计算两个非负整数a,b的最大公约数。1
2
3
4function gcd(a, b) {
if (a % b == 0) return b;
return gcd(b, a % b);
}函数式编程是声明式的一种类型,声明式强调目标而不是具体过程。
Summary 4.
- D&C将问题逐步分解。使用D&C处理列表时,基线条件很可能是空数组或只包含一个元素的数组。
- 实现快速排序时,请随机地选择用作基准值的元素。快速排序的平均运行时间为 O(n * logn)。
- 大O表示法中的常量有时候事关重大,这就是快速排序比归并排序快的原因所在。
- 比较简单查找和二分查找时,常量几乎无关紧要,因为列表很长时,O(logn)的速度比O(n)快很多。
五、散列表 Hash tabe
- 散列函数总是将同样的输入映射到相同的索引。每次你输入avocado,得到的都是同一个数字。因此,你可首先使用它来确定将鳄梨的价格存储在什么地方,并在以后使用它来确定鳄梨的价格存储在什么地方。
- 散列函数将不同的输入映射到不同的索引。avocado映射到索引4,milk映射到索引0。每种商品都映射到数组的不同位置,让你能够将其价格存储到这里。
- 散列函数知道数组有多大,只返回有效的索引。如果数组包含5个元素,散列函数就不会返回无效索引100。
- O(1) - O(n)
- 在平均情况下,散列表的查找(获取给定索引处的值)速度与数组一样快
- 填装因子大于1意味着商品数量超过了数组的位 置数。一旦填装因子开始增大,你就需要在散列表中添加位置,这被称为调整长度(resizing)。填装因子越低,发生冲突的可能性越小,
散列表的性能越高。 - 一旦填装因子超过0.7,就该调整散列表的长度。
Summary 5.
散列表适用于:
- 模拟映射关系;
- 防止重复;
- 缓存/记住数据,以免服务器再通过处理来生成它们。
而要避免冲突,需要有:
- 较低的填装因子;
- 良好的散列函数。
六、广度优先搜索 Breadth-first Search
- 图用于模拟一组连接。图由节点和边组成。一个节点可能与众多节点直接相连,这些节点被称为邻居。
- 广度优先搜索:
- 广度优先搜索指出是否有从A到B的路径。
- 如果有,广度优先搜索将找出最短路径。
- 面临类似于寻找最短路径的问题时,可尝试使用图来建立模型,再使用广度优先搜索来
解决问题。 - 有向图中的边为箭头,箭头的方向指定了关系的方向,例如,rama→adit表示rama欠adit钱。
- 无向图中的边不带箭头,其中的关系是双向的,例如,ross - rachel表示“ross与rachel约
会,而rachel也与ross约会”。 - 队列是先进先出(FIFO)的。
- 栈是后进先出(LIFO)的。
- 你需要按加入顺序检查搜索列表中的人,否则找到的就不是最短路径,因此搜索列表必
须是队列。 - 对于检查过的人,务必不要再去检查,否则可能导致无限循环。
七、狄克斯特拉算法
- 广度优先搜索找出的是段数最少的路径。如果要找出最快的路径可使用另一种算法——狄克斯特拉算法(Dijkstra’s algorithm)。
- 找出最便宜的(权重最小)节点
- 对于该节点的邻居,检查是否有前往它们的更短路径,如果有,就更新其开销。
- 重复这个过程,直到对图中的每个节点都这样做了。
- 计算最终路径。
- 加权图(weighted graph)
- 非加权图(unweighted graphQ34567809 -=
在无向图中,每条边都是一个环。狄克斯特拉算法只适用于有向无环图(directed acyclic graph,DAG)。
实现
要编写解决这个问题的代码,需要三个散列表。
Summary 7.
- 广度优先搜索用于在非加权图中查找最短路径。
- 狄克斯特拉算法用于在加权图中查找最短路径。
- 仅当权重为正时狄克斯特拉算法才管用。
- 如果图中包含负权边,请使用贝尔曼福德算法。
八、贪婪算法
- 每步都选择局部最优解,最终得到的就是全局最优解。
教室分配课程:
(1) 选出结束最早的课,它就是要在这间教室上的第一堂课。
(2) 接下来,必须选择第一堂课结束后才开始的课。同样,你选择结束最早的课,这将是要
在这间教室上的第二堂课。
NP完全问题
- 元素较少时算法的运行速度非常快,但随着元素数量的增加,速度会变得非常慢。
- 涉及“所有组合”的问题通常是NP完全问题。
- 不能将问题分成小问题,必须考虑各种可能的情况。这可能是NP完全问题。
- 如果问题涉及序列(如旅行商问题中的城市序列)且难以解决,它可能就是NP完全问题。
- 如果问题涉及集合(如广播台集合)且难以解决,它可能就是NP完全问题。
- 如果问题可转换为集合覆盖问题或旅行商问题,那它肯定是NP完全问题。
Summary 8.
- 贪婪算法寻找局部最优解,企图以这种方式获得全局最优解。
- 对于NP完全问题,还没有找到快速解决方案。
- 面临NP完全问题时,最佳的做法是使用近似算法。
- 贪婪算法易于实现、运行速度快,是不错的近似算法。
九、动态规划
- 动态规划从小问题着手,逐步解决大问题
Summary 9.
需要在给定约束条件下优化某种指标时,动态规划很有用。
- 问题可分解为离散子问题时,可使用动态规划来解决。
- 每种动态规划解决方案都涉及网格。
- 单元格中的值通常就是你要优化的值。
- 每个单元格都是一个子问题,因此你需要考虑如何将问题分解为子问题。
- 没有放之四海皆准的计算动态规划解决方案的公式。
十、K最近邻算法
- K:和目标最近的K个近邻
特征抽取
- 抽取它们的特征,再根据特征绘图
- 计算两坐标距离
OCR指的是光学字符识别,使用KNN算法
- OCR的第一步是查看大量的数字图像并提取特征,这被称为训练(training)。大多数机器学
习算法都包含训练的步骤:要让计算机完成任务,必须先训练它。下一个示例是垃圾邮件过滤器,
其中也包含训练的步骤。
Summary 10.
KNN用于分类和回归,需要考虑最近的邻居。
- 分类就是编组。
- 回归就是预测结果(如数字)。
- 特征抽取意味着将物品(如水果或用户)转换为一系列可比较的数字。
- 能否挑选合适的特征事关KNN算法的成败。