《算法图解》——学习笔记

第一章 二分查找

使用二分查找时,必须保证列表有序,每次都排除一半的数字。

仅知道算法需要多长时间才能运行完毕还不够,还需知道运行时间如何随列表增长而增加。这正是大O表示法的用武之地。

大O表示法指出了算法有多快。并非以秒为单位的速度。大O表示法让你能够比较操作数,它指出了算法运行时间的增速。

大O表示法指出了最糟情况下的运行时间。

O(n)时间意味着查看列表中的每个元素一次。

第二章 选择排序

内存管理

数组:浪费内存、转移

链表的优势在插入元素方面,在随机读取数据时,数组的效率很高。而链表只能顺序访问。
在这里插入图片描述

第三章 递归

递归只是让解决方案更清晰,并没有性能上的优势。实际上,在有些情况下,使用循环的性能更好。我很喜欢Leigh Caldwell在Stack Overflow上说的一句话:“如果使用循环,程序的性能可能更高;如果使用递归,程序可能更容易理解。如何选择要看什么对你来说更重要。”

编写递归函数时,必须告诉它何时停止递归。正因为如此,每个递归函数都有两部分:基线 条件(base case)和递归条件(recursive case)。递归条件指的是函数调用自己,而基线条件则指的是函数不再调用自己,从而避免形成无限循环。

调用栈

用于存储多个函数的变量。所有函数调用都进入调用栈。

调用另一个函数时,当前函数暂停并处于未完成状态。该函数的所有变量的值都还在内存中。

使用栈虽然很方便,但是也要付出代价:

存储详尽的信息可能占用大量的内存。每个函数调用都要占用一定的内存,如果栈很高,就意味着计算机存储了大量函数调用的信息。

第四章 快速排序

D&C的工作原理:
(1) 找出简单的基线条件;

(2) 确定如何缩小问题的规模,使其符合基线条件。

编写涉及数组的递归函数时,基线条件通常是数组为空或只包含一个元素。陷入困境时,请检查基线条件是不是这样的。

快速排序是一种常用的排序算法,比选择排序快得多。

快速排序也使用了D&C。

对排序算法来说,最简单的数组就是根本不需要排序的数组。

快速排序的工作原理
首先,从数组中选择一个元素,这个元素被称为基准值。

接下来,找出比基准值小的元素以及比基准值大的元素。这被称为分区。

对这两个子数组进行快速排序,再合并结果。

(1) 选择基准值。

(2) 将数组分成两个子数组:小于基准值的元素和大于基准值的元素。

(3) 对这两个子数组进行快速排序。

快速排序对任何长度的数组都管用。

快速排序的性能高度依赖于你选择的基准值。假设你总是将第一个元素用作基准值,且要处 理的数组是有序的。由于快速排序算法不检查输入数组是否有序,因此它依然尝试对其进行排序。
层数为O(log n)(用技术术语说,调用栈的高度为O(log n)),而每层需要的时间为O(n)。因此整个算法需要的时间为O(n) * O(log n) = O(n log n)。这就是最佳情况。
在最糟情况下,有O(n)层,因此该算法的运行时间为O(n) * O(n) = O(n2)。

大O运行时间–最坏---------平均--------最好

选择排序 O(n2)-------O(n2)-------O(n)

快速排序 O(n2)-------O(nlog n)—O(nlog n)

归并排序 O(nlog n)—O(nlog n)–O(nlog n)

第五章 散列表

散列函数:将输入映射到数字。

数组和链表都被直接映射到内存,但散列表更复杂,它使用散列函数来确定元素的存储位置。

应用:

①查找(模拟映射关系)

②防止重复

③缓存数据

缓存的工作原理:网站将数据记住,而不再重新计算。

缓存是一种常用的加速方式,所有大型网站都使用缓存,而缓存的数据则存储在散列表中!

冲突

给两个键分配的位置相同。

处理冲突的方式很多,最简单的办法如下:如果两个键映射到了同一个位置,就在这个位置存储一个链表。

散列函数很重要,好的散列函数很少导致冲突。最理想的情况是,散列函数将键均匀地映射到散列表的不同位置。

在平均情况下,散列表执行各种操作的时间都为O(1)。O(1)被称为常量时间。它并不意味着马上,而是说不管散列表多大,所需的时间都相同。
在这里插入图片描述

在平均情况下,散列表的查找(获取给定索引处的值)速度与数组一样快,而插入和删除速 度与链表一样快,因此它兼具两者的优点!但在最糟情况下,散列表的各种操作的速度都很慢。 因此,在使用散列表时,避开最糟情况至关重要。
为此,需要避免冲突。而要避免冲突,需要有:

①较低的填装因子;

②良好的散列函数。

填装因子

填装因子度量的是散列表中有多少位置是空的。

填装因子大于1意味着商品数量超过了数组的位置数。一旦填装因子开始增大,你就需要在散列表中添加位置,这被称为调整长度。
平均而言,即便考虑到调整长度所需的时间,散列表操作所需的时间也为O(1)。

良好的散列函数

良好的散列函数让数组中的值呈均匀分布。如SHA函数。

第六章 广度优先搜索

解决最短路径问题的算法被称为广度优先搜索。

广度优先搜索可回答两类问题。
第一类问题:从节点A出发,有前往节点B的路径吗?(在你的人际关系网中,有芒果销售商吗?)

第二类问题:从节点A出发,前往节点B的哪条路径最短?(哪个芒果销售商与你的关系最近?)

搜索列表:队列

实现图:散列表

广度优先搜索的运行时间为 O(人数 + 边数),这通常写作O(V + E),其中V为顶点(vertice)数,E为边数。

第七章 狄克斯特拉算法

在狄克斯特拉算法中,你给每段都分配了一个数字或权重,因此狄克斯特拉算法找出的是总权重最小的路径。

狄克斯特拉算法包含4个步骤。
(1) 找出最便宜的节点,即可在最短时间内前往的节点。

(2) 对于该节点的邻居,检查是否有前往它们的更短路径,如果有,就更新其开销。

(3) 重复这个过程,直到对图中的每个节点都这样做了。

(4) 计算最终路径。

广度优先搜索用于在非加权图中查找最短路径。

狄克斯特拉算法用于在加权图中查找最短路径。

仅当权重为正时狄克斯特拉算法才管用。

第八章 贪婪算法

贪婪算法很简单:每步都采取最优的做法。用专业术语说,就是你每步都选择局部最优解,最终得到的就是全局最优解。

在有些情况下,完美是优秀的敌人。有时候,你只需找到一个能够大致解决问题的算法,此时贪婪算法正好可派上用场,因为它们实现起来很容易,得到的结果又与正确结果相当接近。

近似算法

NP完全问题

贪婪算法寻找局部最优解,企图以这种方式获得全局最优解。

对于NP完全问题,还没有找到快速解决方案。

面临NP完全问题时,最佳的做法是使用近似算法。

贪婪算法易于实现、运行速度快,是不错的近似算法。

第九章 动态规划

动态规划先解决子问题,再逐步解决大问题。

动态规划功能强大,它能够解决子问题并使用这些答案来解决大问题。但仅当每个子问题都是离散的,即不依赖于其他子问题时,动态规划才管用。

动态规划可帮助你在给定约束条件下找到最优解。

在问题可分解为彼此独立且离散的子问题时,就可使用动态规划来解决。

每种动态规划解决方案都涉及网格。

单元格中的值通常就是你要优化的值。

每个单元格都是一个子问题。

动态规划的应用

①根据最长公共序列来确定DNA链的相似性。

②git diff命令指出两个文件的差异。

③编辑距离指出两个字符串的相似程度。

④断字功能的应用程序确定在什么地方断字以确保行长一致。

第十章 K最近邻算法

KNN用于分类和回归,需要考虑最近的邻居。

创建推荐系统

特征抽取(挑选合适的特征很重要)

特征抽取意味着将物品(如水果或用户)转换为一系列可比较的数字。

计算两位用户的距离,使用距离公式。在实际中经常使用余弦相似度。余弦相似度不计算两个矢量的距离,而比较它们的角度。

回归
使用KNN来做两项基本工作——分类和回归。

分类就是编组;回归就是预测结果(一个数字)。

第十一章 接下来如何做

二叉查找树

对于其中的每个节点,左子节点的值都比它小,而右子节点的值都比它大。

在二叉查找树中查找节点时,平均运行时间 O(log n),但在糟的情况下所需时间为O(n);而在有序数组中查找时,即便是在糟情况下所需的时间也只有O(log n)。然而,二叉查找树的插入和删除操作的速度要快得多。
二叉查找树也存在一些缺点,例如,不能随机访问,在二叉查找树处于平衡状态时,平均访问时间也为O(log n)。

反向索引

常用于创建搜索引擎

创建一个散列表,这个散列表的键为单词,值为包含指定单词的页面。

并行算法

速度的提升并非线性的。原因:并行性管理开销,负载均衡。

MapReduce(分布式算法)

分布式算法非常适合用于在短时间内完成海量工作,其中的MapReduce基于两个简单的理 念:映射(map)函数和归并(reduce)函数。

映射函数

映射函数很简单,它接受一个数组,并对其中的每个元素执行同样的处理。

归并函数

理念是将很多项归并为一项。

MapReduce使用这两个简单概念在多台计算机上执行数据查询。

布隆过滤器

布隆过滤器是一种概率型数据结构,它提供的答案有可能不对,但很可能是正确的。布隆过滤器的优点在于占用的存储空间很少,非常适合用于不要求答案绝对准确的情况。

HyperLogLog

HyperLogLog是一种类似于布隆过滤器的算法。近似地计算集合中不同的元素数,与布隆过滤器一样,它不能给出准确的答案,但也八九不离十,而占用的内存空间却少得多。

SHA算法

给定一个字符串,SHA 返回其散列值。

SHA是一个散列函数,它生成一个散列值——一个较短的字符串。用于创建散列表的散列函数根据字符串生成数组索引,而SHA根据字符串生成另一个字符串。

可使用SHA来判断两个文件是否相同,这在比较超大型文件时很有用。计算它们的SHA散列值,再对结果进行比较。

检查密码

SHA还让你能在不知道原始字符串的情况下对其进行比较。SHA被广泛用于计算密码的散列值。这种散列算法是单向的。

局部敏感的散列算法

SHA还有一个重要特征,那就是局部不敏感的。

使用Simhash,如果你对字符串做细微的修改,Simhash生成的散列值也只存在细微的差别。

Diffie-Hellman密钥交换

Diffie-Hellman算法解决了如下两个问题:

双方无需知道加密算法。他们不必会面协商要使用的加密算法。

要破解加密的消息比登天还难。

Diffie-Hellman使用两个密钥:公钥和私钥。

Diffie-Hellman算法及其替代者RSA依然被广泛使用。

线性规划

线性规划用于在给定约束条件下大限度地改善指定的指标。线性规划是一个宽泛得多的框架,图问题只是其中的一个子集。

猜你喜欢

转载自blog.csdn.net/sinat_34341162/article/details/83450106
今日推荐