《算法神探:一部谷歌首席工程师写的CS小说》[美]Jeremy Kubica(著)啊哈磊、李嘉浩(译)中国工信出版集团+电子工业出版社 2017年2月第1版 ISBN 978-7-121-30764-5 The CS Detective / Jeremy Kubica / 2016 博文视点
搜索问题的定义为:任何需要我们在可能的空间范围(搜索空间)内,找到一个特定值(即目标)的问题。(p6)
用穷举搜索算法搜索目标值是要在整个搜索空间范围内尝试每一种可能性。最常见的穷举搜索是线性搜索,即按照顺序简单的检查所有不同的可能性。(p13)
数组是可以让你存储多个值的简单数据结构。一个数组就像一排箱子一样,每个箱子可以储存一条信息,例如一个数或一个字符。数组结构的意义在于,可以通过指定一个位置或索引的方法来存储和读取数组中的任何值(或元素)。
数组不仅可以存储一列数字,它们还可用于存储由一列字母组成的字符串。数组中的每一位都可容纳一个字符,这个字符可以是字母、数字、符号和空格。(p26)
二分搜索算法的工作原理是:不断的将搜索空间分成两半,而且把搜索空间限制在其中的一半中。(p34)
对于二分搜索,我们得了解有待排序的数据的相关信息,以便知道数据是按照什么方式排序的。为了排除(或缩小)较大查找范围,所使用的算法必须能够保证我们要找的目标值不在被去除的范围内。但是,按照某个维度对数据排序后,并不意味着你可以按照另一个维度对数据进行二分搜索。(p43-44)
二分搜索法背后的基本思想——利用数据中的规律去不断地将搜索的范围减半——远比二分搜索法具体的应用更重要。有了这个基本思想,你可以调整二分搜索法,让它适用于环形的有序数组。(p54)
搜索在遇到死路时一定要学会如何倒退。简单的说,就是倒退一步,退回到之前一步的状态,然后选择另一条不同的路继续搜索下去。(p67)
广度优先搜索是一个按顺序依次尝试可能的搜索选项的算法。换句话说,它每次都会选择尝试最先发现的但还没有尝试过的选项。值得注意的是,广度优先搜索只有在相邻点之间移动的代价都一样时才会给出最优的方案。(p76-77)[2017-08-28读完]《算法神探:一部谷歌首席工程师写的CS小说》读书摘录
深度优先搜索会优先考虑最近新遇到的搜索状态,所以算法会沿着一条路往下走,直到遇到目标状态,或者一条死路。 (p89)[2017-08-28读完]《算法神探:一部谷歌首席工程师写的CS小说》读书摘录
栈和队列是用来存放数据的两种简单的数据结构,它们和列表的区别主要体现在添加和删除数据的方式上。栈是后入先出的数据结构;而队列是一个先入先出的数据结构。(p97-98)
深度优先搜索和广度优先搜索这两种算法在概念上是相似的,但用来储存状态的数据结构(栈或队列)不同,这在很大程度上决定了搜索会怎么进行下去。(p106)
并行算法所做的是,将一个问题分成数个小块,并同时在这些小块上执行计算,最后再将结果组合起来。当你考虑是否应该使用并行计算时,需要考虑的方面是,并行计算带来的效率提升是否大于它所带来的额外工作量。(p115-116)
迭代加深是深度优先搜索的一种改版,它限制了每一次搜索的深度,在第k轮搜索的时候,这个算法会执行一次深度限制(max-depth)为k的深度优先搜索。(p125)
逆向索引是计算中要用的一种数据结构,它和书的索引类似。对于每一个值,逆向索引可以告诉你这个值在数据中的哪些地方出现过。如果一个值会在数据中反复出现,这一点就格外有用。逆向索引是一个很典型的需要在运行时间和内存占用两者之间取舍的例子。添加一个逆向索引会占到更多的内存,但它也让我们在一个新的维度上进行搜索的效率得到了极大提升。(p132-133)
二叉搜索树是一个数据结构,它储存信息的方式和二分搜索中使用信息的方式类似。树中的每一个节点存放一个值,并且每个节点最多有两个子节点:一个左子节点和一个右子节点子。节点的位置根据其中存放的值的大小来定。所有左子节点和它的左子节点中存放的值都比当前节点中的值小;类似的,所有右子节点和它的右子节点中存放的值都比当前节点的值大。(p142)[2017-08-28读完]《算法神探:一部谷歌首席工程师写的CS小说》读书摘录
你可以从一个排好序的数组中创建出一棵二叉搜索树。你只需要不断递归地将所有元素划分成更小的集合,在每一层中选择中间的那个元素作为那一层的节点。如果元素个数为偶数,那么随意选择中间两个元素中的一个就行。(p150)
在二叉搜索树上进行区间查找和查找一个元素类似。算法从最顶端的根节点开始,一路递归着搜索整棵树。使用二叉搜索树来做区间搜索的优势在于,可以通过剪去大量不可能包含要找的值的分支来减少计算量。(p160)
像二叉搜索树中加入节点和寻找一个节点类似。我们从根节点开始逐步向下,操作就和我们寻找想加入的值一样。我们通过比较想加入的值和当前节点的值的大小,来决定是该向左下还是向右下走。当我们遇到死路时,也就是需要走的方向的子节点并不存在时,我们便将要加入的值插入到这个不存在的子节点的位置。(p169)
我们还可以往二叉搜索树中添加和删除节点。但是,每当我们更改了原有数据的结构时,必须要确保不能破坏二叉搜索树的属性,这非常重要。(p173)
trie树是基于树的数据结构,用户可以很方便地通过某个字符串的前缀来搜索到目标字符串。与二叉搜索树一样,trie树也是首先从根节点开始,然后一步步向下选取分支节点。在树中,每一个节点下的分支数(即有多少个子节点),取决于所有字符串中当前节点字母的下一个元素有多少种不同的可能。所以,trie树的每个节点可能不止两个子节点。(p179)
最佳优先搜索是基于某种估值分数或者评价函数来选择接下来搜索哪个状态的算法。最佳优先搜索就是在每时每刻维护着一个带有估值分数的状态列表,每次从这个有序列表取出下一个估值分数最高的状态去搜索。(p190)
就像数据栈和队列,优先队列数据结构让你能插入数据,然后按特定的顺序删除数据。栈和队列的执行顺序由插入的元素决定,而优先队列通过优先级递减来给数据排序。下一个删除的元素是当前队列中优先级最高的元素,而无论该元素是何时插入的。(p199)
从概念上说,最佳优先搜索类似于广度优先搜索和深度优先搜索——在算法的每一步,都会选择一个新状态来进行探索。它们之间最关键的区别在于如何安排新产生的状态的探索次序。使用优先队列可以让我们每次都更有效地挑选出最接近目标解的状态。最佳优先搜索与优先队列是一对完美的组合,是一组极其高效的“数据结构+算法”的范例。(p205)
启发式搜索是依据经验来帮助算法快速达到目标。要注意启发式搜索并不是胡乱猜测,而是需要在包含一些有用信息的基础上根据具体问题情况正确使用。(p210-211)
最大堆是基于二叉搜索树的数据结构,它的每个节点与其子节点之间需要时刻维持有序关系。具体来说,堆在存储元素时一定要遵循堆的特性,对于最大堆,树中的任意一个节点的值都要大于(或等于)其下面的所有节点。这种结构允许最大堆高效地支持几个非常重要的操作:(1)找到最大的元素,(2)删除最大的元素,(3)插入任意元素。这三个操作使得堆成为实现优先级队列的理想数据结构。(p219)
如果你从这门课中只学到了一样东西,那应该是:高效算法的关键在于信息。当遇到一个新的问题时,应该花些时间理解这个问题的结构和它的数据。问题拥有的结构越多,你越有可能使用这些信息。正如你所看到的,在一个有序数组中找到一个值比在一个完全随机的数组中找到一个值要简单得多。有时候,你甚至可以建立附属的数据结构,例如堆或逆向索引,以提供所需要的结构。尽管如此,解决问题的第一步始终应该是理解问题。(p229)
网友评论