好书一块儿读(85):算法笔记

这阵子看了两本算法书,《算法》和《算法导论》。java

前一本读着很轻松,内容基本与大学数据结构课程重叠,示例代码用java编写,学习曲线平缓,对应用程序员来讲,读它就挺好。程序员

后一本我是边看麻省理工的《算法导论》公开课边读的,力不从心,由于我数学基础很差(详下),若是不看数学证实,其内容跟前一本就差很少了,数学基础比较好、对算法感兴趣的朋友,能够尝试之。算法

强烈建议各公开课平台的学习资源,质量很是高,相见恨晚!足不出户,就能学习名校的课程。但学习时别忘了跟着作习题、作笔记、预习复习。编程

下面是个人《算法》笔记。数组

 

零、思想数据结构

重中之重。算法真正的精髓。内功心法所在。相比之下,后面讲到的都是具体招式。数据结构和算法

内功心法的意义就在,有朝一日你遇到一个没有既有经验的问题,能本身设计出适当的数据结构和算法,而不是只会用别人的轮子。函数

算法的根本思想,是大问题化为多个小问题,逐渐解决之。(《孙子兵法》:治众如治寡,分数是也;斗众如斗寡,形名是也。)工具

1.分治学习

若是能将大问题拆为同性质的小问题,当小到必定程度,能直接解决之。

则采用分治法,递归解决。

2.贪心

若是为小问题找到的最优解,能经过增长一点步骤,解决一个大一点的问题,并依然是最优解。

则采用贪心法,从0开始,逐步蚕食掉大问题。

3.动态规划

若是用分治法拆出的小问题中常见到相同问题,重复计算它们,成本过高。

则采用备忘法,计算完每一个小问题的结果后,记录之,下次见到同一小问题,再也不重复计算。

 

1、数学基础

《算法导论》在附录中给出了4项必备的数学基础。

我看了以后,很羡慕考研的同窗,他们这几门课都很扎实。

这4门数学除了是考研数学科目、算法的基础外,其实更是观察世界、指导生活(不只仅是生产!)的工具,我决定要以看公开课的方式,逐步从新学之。

1.微积分

2.离散数学

3.几率论

4.线性代数

 

2、线性表

1.按实现分类

顺序表:随机访问方便,插入删除成本高,由于一侧的元素要「哒哒哒」依次移动一格。

链表:随机访问麻烦,由于要从头「一二三」数过去,插入删除方便,将特定两三个节点的指针「咔吧」从新接一下就行了。

2.按做用分类

栈:后进先出,直观理解就是坐电梯。用处主要在处理大问题时,细化为小问题,小问题解决完再继续处理大问题的时候。计算机的函数调用的核心数据结构。若是不幸小问题没完没了,如无限递归,则stackoverflow。

队列:先进先出,讲「先来后到」的数据结构。典型使用就是「任务队列」,先到的先处理。若是容许「插队」,就是「优先级队列」了,让领导先走!

 

3、排序

假设有一个初始队列4132,目标是从小到大排序成1234。

1.冒泡排序

最简单也最慢的算法,慢到很多书都不讲了。

思想是让最后一个元素尽力向上爬,碰见比它大的就压过之(交换次序),若是遇到比它小的就停下,由小的继续往上爬。爬到第一个元素时,就保证了第一个元素是所有元素中最小的一个。

再也不考虑第一个元素,重复这一过程。4132,1/423,12/43,123/4。

2.选择排序

每次都从队列中选出一个最小的元素,放到「已排序队列」里,直到选完。

/4132,1/432,12/43,12/34。

3.插入排序

每次都从「未排序队列」中选第一个元素,插到「已排序队列」的适当位置,直到插完。

我玩扑克时理牌就用这法。

4/132,14/32,134/2,1234

4.希尔排序

先把「相距较大间距」的元素们排序一遍,再适度减少间距再排一遍,最后一次相距零间距排。

4132,先间隔较大间距(这个简陋例子,姑且间隔1),4与3排序,1与2排序,得3142,再间隔较小间距(本例中直接到0了)排序。

5.快速排序

最被喜欢的排序,既容易理解,又快。

选一个数字为轴,比它小的放左边,比它大的放右边,而后对其两边各进行这一变化便可。

4132,第一步选4,第二步选1,都是把所有剩余元素放到同侧,不划算,因此随机选择轴元素很关键。

假设第一步选了3,12放左侧,4放右侧,右侧完结,左侧继续,假设选2为轴,1放左侧,右侧没有,即得结果1234。

6.归并排序

所谓归并,好比一班和二班已经各排成了从矮到高的有序队列,想归并成一队,怎么办?比较一班和二班的排头,更矮的选出来站到「结果队列」里,该班的新排头再跟对方排头比,以此类推。

归并成一个新队列后,再跟别的队列归并,直到通通归并为一队。

14/23 -> 1|4/23 -> 12|4/3 -> 123|4 -> 1234

7.堆排序

所谓堆,就是一棵每一个父节点数值都大于其一切子孙节点数值的彻底二叉树,若是看不懂,先跳到下面看树那节。

为方便说(放图太麻烦了,我相信纯文字能说清),此次我们从大到小排序,道理固然是同样的。

分两步,一是从无序队列构造树,二是从树输出有效队列,结合起来,就是从无序队列,获得了有序队列。

第一步,拿一个新节点,放到原树的最后一个位置,而后使之尽力向上冒泡,直到冒不动为止,再拿下一个新节点。如处理4132:

4 -> 4左下1 -> 4左下1右下3 -> 4左下1右下3,1左下2 -(2冒泡)-> 4左下2右下3,2左下1

第二步,取出根节点放到结果队列,把最后一个节点僭越到根位置,而后考察其两个叶子节点,若是有更有实力的,则向最有实力的退位让贤,交换位置,到新岗位后继续考察下属能力,直到降到本身胜任的位置。

4左下2右下3,2左下1 -(取出4,1上位)-> 1左下2右下3 -(3篡位)-> 3左下2右下1 -(取出3,1上位)-> 1左下2 -(2篡位)-> 2左下1 -(取出2)-> 1孤家寡人 -(取出1)-> 结果4321

 

4、树

若干节点,及若干链接节点的无向边,构成的图,若是从每一个节点出发,都能到达任意一个节点(强连通),同时图中不存在环,即称为树。

数据结构中说的树,在此基础上再严格点,分为若干层。如某团长手下有若干连长,每一个连长手下有若干班长,画组织结构图,把直接领导和直接下属链接起来。直接领导称为父节点,如团长是连长的父节点,连长是其属下每一个班长的父节点;直接下属称为子节点;最高长官称为根节点,即没有父节点的节点;没有子节点的节点称为叶子节点,好比《士兵突击》里钢七连只剩老七(连长)和三多(班长)的时候,三可能是叶子节点,若是这时三多被老A挖走(实际剧情是老七先走,不赘),则老七堂堂连长,将不幸成为叶子节点。

1.二叉树

若是树的每一个父节点最多只有两个子节点,该树即称为二叉树。

最典型用途是二叉查找,即若是每一个根节点数值都比其左子树(此概念望文生义,不赘)任意节点数值更大,比其右子树的任意节点数值更小,该树即称为二叉排序树。在想寻找某个数值时,能够先将该数值与根节点比,若是比根节点大,则去与其右子树根节点比,若是比根节点小,则去与其左子树根节点比,直到找到或比到叶子节点为止。

好像「猜数字」游戏,一本好书价值1元到256元以内,假设你蠢到看不出那书不可能1元,又聪明到知道采用二叉查找,则该先猜128元,根据主持人说「高了」「低了」再决定猜64仍是192元,假如64仍是「高了」,则该继续猜32,以此类推。

2.红黑树

强烈推荐看《算法》中红黑树的章节,从2-3树开始讲,讲到红黑树,学习曲线很是平滑。

二叉树和红黑树都有着清晰的用于保持树平衡的逻辑,限于篇幅很少讲。平衡的目的是保持最差状况下的查找程度为logN(以2为底,总节点数的对数)。

3.散列表

这个不是树……但在逻辑上放在这里最好,由于都是用于「查找」的数据结构。

编程中最基本的两种数据结构,list和map,list就是上文说的线性表,核心是有顺序,map就是这里说的散列表(hashmap)或树(treemap),核心是键值对。set呢,多以map作底层结构。

树上面说了,这里说散列表。

散列表又叫字典,从这字面可知,主要用途是查找,经过键(key)查找值(value),首先,要把键算成一个数字,这算法(散列函数)就是java里类们都要实现的hashcode方法。

而后,有两种可选的实现方式,一是连接法,首先有个主list装着键们,好比123,而后1对应一个list,2对应一个list,3对应一个list,我把键算出是1以后,就到1对应的list元素里去找元素。显然,1对应的list长度越短,查找时间越短,那彷佛主list的键们越多越好,好比1到30,那每一个子list的长度将变为原长度的十分之一,但主list的键们也不能太多,好比1到30000,那大部分键对应的实际上是空,里面一个元素都没有,就浪费空间了,因此要权衡。

另外一种实现方式是散列法,不存在次list,把键算成数字后,直接往主list对应的位置存,若是该位置已经被别的元素占了(碰撞)怎么办?则用某种算法,算出一个新数,再尝试往新位置放(再散列),以此类推。显然,这种方法,主list的长度也很关键,若是过短,则碰撞太多,插入、查找都麻烦,若是太长,又浪费地方。

对两种方法,散列函数都很是重要,若是全部元素的散列函数都计算出同一个值,散列表就毫无心义了。

 

5、图

终于到图了!这是我最喜欢的部分,好玩,并且熟悉,由于我认识一个同窗,数据结构年年挂,直到大四,因此我每一年都陪他复习备考一次,老师考卷中的高分的题目,就是考图的几个算法,迪杰斯特拉,普里姆,克鲁斯卡尔……好怀念那段日子。

什么是图?一堆节点,及若干边,就构成了图,根据线路是否有方向性,分为有向图、无向图,根据图中是否有环,分为有环图、无环图。

若是任意两个节点都有线路链接,则称为强连通图,这里主要聊的都是这种。

1.深度优先、广度优先遍历

A连B,B连C,B连D,C连E

想把图里全部节点都研究一遍,怎么办?

从一个节点开始,走向下一个节点,而后再走向下一个节点的下一个节点,直到「眼前无路想回头」,就是「深度优先遍历」。在例子里,从A到B,从B到C,从C到E,E回头到C,C回头到B,从B到D,完成遍历。

「广度优先遍历」则是,从一个节点开始,先扇面状把能到的节点都扫一遍(广嘛),而后再「移步」到子节点们。在例子里,从A到B,从B到C,从B到D,从C到E,完成遍历。

2.最小生成树

若是树的每条边都有个权值,要求选出总权值最小的一些边,让全部节点都能互相连通,怎么办?

这就是「最小生成树」问题,显然,选出的结果确定是一棵树,树的两个条件,连通和无环,连通没必要讲,为何无环?若是有环,去掉任意一条边,节点们依然相互连通,权值变小了,由此反证,必然无环。

解此问题,两种方法,一是普里姆算法,二是克鲁斯卡尔算法,好专业,终于有点算法的感受了。

其实都很简单,先说普里姆,将图划为「已解决」和「未解决」两个部分,一开始「已解决」为空,「未解决」是全集,首先,任选一个点放到「已解决」,而后考察全部从「已解决」连向「未解决」的边,哪条最短,就把该边连向的「未解决」节点归入「已解决」集合,如此反复,直到所有节点都「已解决」,结果就是最小生成树。

再说克鲁斯卡尔,一开始,把每一个节点视为一个「份量」,寻找所有「链接两个不一样份量的边」中最短的一条,将其链接的两个份量合并为一个份量,并重复这一行为,直到所有节点都属于同一份量,结果就是最小生成树。

3.单源最短路径

最小生成树研究的是无向图,最短路径则研究有向图。

顾名思义,最短路径是要求给出从某地到另外某地的最便捷走法,这有两个算法,一是迪杰斯特拉算法,二是贝尔曼福特算法。

先说迪杰斯特拉,这是相似普里姆的「贪婪算法」,从一个极小的「已解决」开始,逐步蚕食,直到将「未解决」吃掉。差异在这回每一个节点上都有个数字,表明其到起点的距离,每次都选所有「从已解决到未解决的边」指向的「未解决」节点中,数字最小的一个。好比起点是家,家距离学校500米,家距离市场600米,学校距离公园200米,根据算法,把学校标注500,市场标注600,500小于600,把学校归入「已解决」,学校进入「已解决」以后,公园也变成了从「已解决」能够到达的「未解决」的节点,用学校的500加200,公园标注为700,与市场的600米相比,600较小,把市场归入「已解决」。注意,这个步骤,若是是普里姆算法,归入的是公园而非市场,由于普里姆不存在起点,都连上就ok,只考虑哪条边最短,但迪杰斯特拉有起点,要比较的是「从起点出发直到这里的距离」。

再说贝尔曼福特,这算法是把每一个节点赋予一个表示「距离起点有多远」的数值,除了起点外,其余起点一开始都是无穷大,而后依次考察每条边,若是这条边能让指向的节点的数值变小,就将该节点数值变小(收缩),把全部边都考察一遍以后,若是这一遍中执行了「收缩操做」,则再把全部边都考察一遍,直到某一遍彻底没收缩为止。上面的例子,好比考察次序是200-500-600,则第一遍200啥用没有,500将学校的+∞变为500,600将市场的+∞变为600,这遍进行了收缩,再来一遍,这回200有用了,将公园的+∞变为700,500和600都没有,这遍也收缩了,再来一遍,200/500/600都没用,完结。

 

6、字符串

字符串本质是字符数组。说到算法,最常说的就是字符串查找的KMP算法。

好比想从「子龙子龙世无双」里寻找「子龙子翼」,「子」等于「子」,「龙」等于「龙」,「子」等于「子」,「龙」不等于「翼」,糟糕!这时源文本的「已搜索序列」的「后缀们」有「子龙子龙」「龙子龙」「子龙」「龙」四个,而目标文本的「前缀们」有「子龙子翼」「子龙子」「子龙」「子」四个,考察两个「们」,发现有个相同元素,即「子龙」,长度为2(若是有多个相同元素,则取最长的),意味着源文本的「已搜索序列」中最后两个字与目标文本的前两个字相同,下次用源文本的下一个字「世」与目标文本的第三个字「子」相比便可。若是是朴素算法,则要用「子龙子龙世无双」的第一个「龙」字与「子龙子翼」的第一个「子」字相比,KMP算法比朴素算法快多了!

 

7、写在后面

算法的水太深,我仅仅入了个门,回头看看,上面所写,全是大学《数据结构》的内容,长叹一声。

也有更深刻学习的意愿,但在这以前要巩固数学基础,我在看网易公开课上的《线性代数》和《微积分》的公开课。

好在,这些「入门知识」,平日工做倒也够用,而数学、计算机基础的巩固和学习,是一生的事,不用太急。

不要哀叹过去的荒芜,惟一的方法是立足于如今的状态,选取最优的进步路线,勤勉地躬行之。

学过一点点经济学基础的人,不会为「沉没成本」沮丧。

 

数学太美,逻辑太美,算法太美。

我却近来才发现。读书的时候全没免得。

不免得也不要紧,老老实实听话就好,如今想一想,是「独立思考」得过于早了。

这是个人人生历程,没什么可后悔,写这只是想规谏还在念书的朋友。

别急着自行其是,之后的人生里,有的是机会让你自行决策,还在念书的时候,请听话。

听老师的话,好好学习,每天向上。

相关文章
相关标签/搜索