感谢FunTester送的书籍。建议大家买或借来看看,一起学习下。本文主要是记录我看这本书的心得,不一定理解是对的,是我自己悟出来的体会,而不是按书照抄,都是凭自己的理解写出来的,相当于在写一本书了,哈哈。。。。十个人看会有十种不同的见解。一定要结合书看,先把书看完,在看看别人的心得,这对自己的提高有一定帮助。还有这本书本来就是算法,肯定跟学数学一样,一定要多思考,不要死记硬背,理科不像文科,理科靠逻辑思维,文科靠记忆。
读书心得是你看完后的体会,读书笔记是你看书哪些重点需要记录的内容。我两者都有了。这本书是 20195月第1版,比新版缺点内容,如:红黑树等
第一章 算法概述
1.算法
什么是算法?书中举了1+2+3+4....+....10000=?,你怎么去计算,你是要一个个加还是有什么技巧。所以我理解算法就是:算数的方法,简称算数的方法,专业就叫算法,你是怎么理解算法的?
当然本书的算法是应用在计算机程序领域,无论是:运算、查找、排序、最优决策/路线、面试(如果这也算的话),我们都需要到算法,就看你要怎么去计算,用到什么方法技巧。这本书大部分就告诉你各个模型的计算方法,也就是别人研究好的最优算法,你只需去理解和应用即可。
当然,不管白猫黑猫,能抓到老鼠就是好猫,无论你怎么算,结果都是对的,但是呢,谁算的更快(效率),算的更简洁(简单)。就像考试,你虽然结果对了,但是写了密密麻麻一页纸,人家仅用几行就算出来了,你说老师给谁高分?这就是我们接下来要讲的,算法有什么影响。
2.数据结构
什么是数据? 数据是用来存的。什么是结构?就是你怎么去存这个数据。站着存还是躺着存。
存数据就存数据,为啥要分类,你还管我怎么存。因为数据你不仅要存,还要取,要修改,这又涉及到高效问题。比如你存个东西进仓库,放在很隐秘的角落,你让我去取,我怎么知道在哪里,找起来多麻烦。又比如下图找出不同的哪吒?
这就是你存放的数据没有关联,没有按一定的规则摆放。你找起来是不是比较困难?
在数据结构中,我们常见的有:
线性结构:最简单的数据结构,就类似一串串连起来似的,如:数组、链表、栈、队列。。。
树:相对较复杂的结构,看着并不复杂,但是遇到的时候,算起来就知道了,就像一颗树看着简单,但是它有很多的枝叶、树根、延绵不断,而且全世界每片叶子也是不重复的,你说它复杂不?
图:那更好理解了,就是有一定的关联性,比如你存东西后,拍照,下次可以凭图片查找。但是还是更为复杂,你想凭一张图就能很快找到东西?
其他数据结构:还有好多好多,我也记不住。反正你想怎么存就怎么存,你可以按照自己的规则来存放,你能快速找到就行。但是在计算机世界,伟人已经帮我们研究了很多种方式。例如:哈希链表、位图等。
3.复杂度
根据上面所讲的,算法啊,数据结构啊,都是说效率的问题,看谁效率高,速度快等。
书上举了两个人完成一个程序,一个人的程序比另个人时间比较久,占用空间内存大。(就是两个人考试写一套题,一个人完成的时间比较久,写了密密麻麻一页纸,另个人很快做完,就只花了几行就得出答案)虽然最后答案是对的,你说谁厉害?
这里只涉及时间和空间的复杂度的概念,还有涉及其他的吗?
3.1时间复杂度
时间复杂度用大O表示,也称为大O表示法。
就是说你无论怎么做,总得有一定的规律,得出一个公式来表达,这就可以计算两个人所耗时多少了,如:O(n)、O(logn)、O(2n)等等。
你要遇到这种时间复杂度,就是看你要怎么去完成,然后的计算规律是什么。这个我也不会去记,我只要了解它的基本概念。
书中举了几个例子,可以把书中例子看完,然后记住那几个公式最好,一些公式的由来,如n、3n、logn、2、0.5n^2+0.5n这些公式的由来,记住以后,后面的算法就不用特意算,直接背出来。
我这里挑个简单的:
1个长度为10cm的面包,你每3分钟吃掉1cm,那么吃完需要多久?
答:这个小学生都会算吧,3*10=30分钟,如果长度是n cm ,需要多久吃完?那就是3n分钟。
那程序就是f(n) = 3n ,即时间复杂度O(f(n))=O(3n)
你2分钟吃1个鸡腿,无论鸡腿多大,你都是2分钟吃1个鸡腿,它就是f(n)=2,它的时间复杂度就是O(1),
如果你每五分钟吃下面包的一半,也就是5分钟吃下8cm,10分钟吃4cm,15分钟吃2cm...即5logn,这种类似二分查找法,公式一般都是logn。以后你会理解的。当然你厉害可以自己计算,不用记。
说白了,时间复杂度就是把程序相对执行新世纪的函数f(n)简化为一个量级(有规律公式),这个数量可以是3n、n、2n等方式
这里有个思考:
就是说现在的计算机越来越先进,计算速度、硬件啥的越来越高科技的超级计算机,为什么还要重视时间复杂度?
书上举了一个例子:
A(运行在旧电脑上)的时间复杂度简单f(n)=100n,B(运行在超级计算机)的时间复杂度复杂f(n)=5n^2,然后不断的增大n的值,看得出的结果对比;
答:发现B越到后面数值越大,速度越慢,就算在快的计算机也会被耗尽。和A的差距越来越明显。
所以时间复杂度对程序运行时很有用的,如果你看好好利用,你的算法越好,你的程序运行越快。
小结:时间复杂度是一个算法运行时间的长短。
也就是说你用这个算法的时候,你耗了多少时间。
3.2空间复杂度
学了时间复杂度,空间就更容易理解了,一个时间,一个空间的区别而已,道理大同小异。
空间复杂度也用大O表示法,所以时间复杂度、空间复杂度都是属于复杂度算法O。
书上举了一个例子:
就是有一串数字,有两个是重复的,找出这两个重复的整数。如:32154972
答:如果每个数都遍历一遍,如3跟前面的数比较,然后在2跟后面的数比较,也要跟前面的数比较还要回顾去跟3比较,这无疑浪费了。怎么提高效率呢?
这里书中引用了字典,就是说你一个个执行,然后记录一次,即Key值,如果后面有相同的,就在key值加1,就很明显了。
如果脑回路跟不上,那就不要跟了,学数学本身就这样,不要强求。只要理解什么概念就行。以后会慢慢领悟的。再说了,我这里只是心得体会,如要想学习看书看视频教学,这里不会图文并茂这么清晰的。
空间复杂度的计算:常亮空间、二维空间、线性空间、递归空间。
小结:空间复杂度就是一个算法在运行过程中临时占用存储空间大小的量度。
就是我在用算法的时间,肯定占用一定的空间内存大小,这应该很好理解。
第二章 数据结构基础
1.数组
这个我都不想讲,无论是科班出身的同学,还是自学编程的同学,这都是常识。但是这里讲的是算法。基本概念我就略过。
数组分为一维数组,二维数组。。。。
1.1一维数组
很好理解,就是一列比较整齐的队伍,你是按身高、还是按年龄等排列,然后有新的元素补充进来,你要怎么快速的插进去。或者你要怎么快速的查找某个人等等。
所以我都还没有看这节,我就知道接下来它要讲什么内容。
一维数组,如下图:
一个队伍,一个空间,就相当于四张床,编号从0~4,你可用安排张三李四王五。。。睡在上面。
数组的特点是什么?一个队伍,是不是有报数,每个人都有自己固定的数。你要查找某个人是不是对应号码查找比较快。但是你要新增或删除一个,那其他人的号数是不是跟着变啊。
所以涉及这些关于数组的新增、删除、查找、替换方式,要考虑它的时间、空间复杂度。具体细节看看书。
面试经常考这个类型,给你串数组,查找不同/相同,对数组进行排序,对数组进行转换,对数组进行。。。。千篇一律,考的是你用什么技巧去完成这个算法,用程序表现出来,后续会讲到。
1.2二维数组
这个感兴趣去了解下,二维数组即是俩个一维数组,如:一维数组:ONEarry[3]={1,2,3}、 二维数组:TWOarry[3],[4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
所以二维数组是基于一维数组增加的一维坐标而已,倍数就是n*m,请看下图:
至于你要怎么利用它,怎么计算,什么技巧,就看你对它的熟悉程度了。举一反三。
2.链表
如果说数组是正规军,那链表就是地下党。数组是有规律有序的(顺序),群组有对应的号码对应的人(具体住址、电话等),但是链表不是(随机),没有知道它的存在,为防止暴露,他们只能单向联系。他们借助这种单线联络方式,灵活隐秘的传递重要信息。
2.1单向链表
其中就是单向链表。如:
A --> B --> C --> D --> NULL
单向链表有两部分组成,一部分是存放数据的变量data,另一部分就是指向下一个节点的指针next,他们一级一级的传递信息。
2.2双向链表
有单向链表肯定就有双向链表,这个就相对复杂些,多了一个指针prev,就如:
NULL <-- A <--> B <--> C <--> D --> NULL
双向链表看着不像是单向联系,但是不会相互影响,比如C叛变了,但它只能出卖B和D,只要B不说,它就没有办法知道A是谁。
所以链表物理上是非连续、非顺序的数据结构。由若干部分组成,有头有尾。
了解链表的概念后,接下来就是对链表进行增删改查的算法解析了,这个具体看书。
最后对比下数组和链表间的复杂度,各有各的优缺点。来,说说看,数组和链表的复杂度各是多少?
数组:查找元素是不是O(1),插入删除是不是O(n)。
链表:查找元素是不是O(n),插入和删除是不是O(1)。
所以数组查找快,删改慢,链表则相反。
3.栈和队列
本书讲了物理结构和逻辑结构,
什么物理结构?我们人是个对象吧,我们人的身体是人体结构吧,是不是实实在在看的见摸得着的东西。
什么是逻辑结构?我们人除了身体,是不是还有思想,思想能看的见摸得着?是不是很抽象的东西。
很明显,思想肯定是依赖于人的身体,人的大脑而存在吧,如果人体消失了,思想还存在吗?也就是说:逻辑结构会依赖于物理结构。
逻辑结构:1.线性结构(如:顺序表、栈、队列)、2.非线性结构(如:树、图);
依赖于
物理结构:1.顺序存储结构(如:数组)、2.链式存储结构(如“链表”);
3.1栈FILO
什么是栈?比如我们吃东西,吃了吐,就相当于只开口一边,一边封口的,先进去的在底部出不去,只能慢慢的往后撤。即 先进后出,后进先出,这就是栈,
至于栈底(底下那层)、栈顶(最上面那层)、入栈(吃下去)、出栈(吐出来),这些都是字面上的意思,很好理解。
栈可以以数组形式、也可以以链表形式出现。
常见的应用场景:面包屑导航,我们一级级的打开,但是要关掉的是不是最后面的step4啊,你能直接先关step1吗?也就是先打开的后关闭。
3.2队列FIFO
什么是队列?比如我们吃东西,吃了拉,就相当于两边都开口想通的,先进的从另个出口出。即先进先出,后进后出,这就是队列,
至于队尾(底下那层)、队头(最上面那层)、入队(吃下去)、出队(拉出来),这些跟上面理解一样,字面意思。
这应用场景就很多,也就是排队原则,谁先来,谁先处理。随处可见。
那有没有既可以先入先出,也可以先入后出?我也第一次看到这种
3.3双端队列
这个就是类似队列的两边开口相通,但是队列是单行车道,这个双端队列是双向车道。
大概就是这样子,比如:有一排车辆先从1进入,你从2出来,那就是先进先出; 那一排车先从1进入,只能4出来,那要退出来,是不是先等后面的车退出去,你才能退?那你就是先进后出。
很少遇到这种,书上都没有过多的介绍,只让我们自己去查阅资料。
那有没有那种VIP的先出呢,只要你是VIP大佬,无论你排在哪里,都安排你有优先特权,VIP就是牛?赶紧办个VIP。
3.4优先队列
我叫它VIP队列,我帮它取得名。
书上没有介绍,因为它不属于线性数据结构了,基本是二叉堆实现,以后再说,98页。
最大优先队列:无论入队顺序,都是当前最大元素优先出队;
最小优先队列:无论入队顺序,都是当前最小元素优先出队;
根据书中的例子,使用线性数据结构是可以实现,但是实际复杂度较高,这时候二叉堆就派上用场了,看后面优先队列的实现。
说说看,栈和队列的复杂度是多少? 是不是O(1)。--大家对复杂度有概念没有?知道每种结构的算法了吧!
所以算法就是算数的方法,是我们用来解决问题用的,果真学好数理化,走遍天下。编程是要数学思维,要学好编程要学会数学,不是先学英语才学编程。
3.5散列表(哈希表)
学编程不需要学英语?学英语是为了干啥?因为技术几乎都是老外发明的,所以很多文档就是英语,你总不能让老外给你中文吧。如果是我们中国人发明的技术,我们才不需要去学英语呢。所以我们学编程需要学习数学,不学英语也可以。
那不会英语咋看技术文档,不用怕,我们有翻译软件,都那些英语厉害的人,做了翻译的,我们只要拿来用即可。你知道我们查英语是不是输入单词,才输出翻译后的中文。这就像我们小时候查字典一样,查找对应的关键,在搜出你要找的词。这么说吧,学校的学生记录本是不是记录着你的学号和你的姓名,这就很快知道你这个人,如果没有学号唯一区分开来,万一出现同姓同名的岂不是蒙圈了。
像这种有一一对应关系的我们叫散列表。也叫哈希表。这下知道什么是哈希表了吧。就是有一个唯一学号Key对应你的姓名值Value的映射关系。即Key-Value映射的集合。
一一对应的关系,我们的复杂度是不是O(1);
3.6哈希函数
什么是哈希函数?
比如你去学校找一个人,你只知道那个人叫张三,,一个学校几千上万个人吧,而且叫张三的数不胜数(还包含同音字的,如章山、张山、章三等),你怎么找?挨个去问问?还是拿个大喇叭喊着,张三你给我出来。这样的效果行的通吗?学校多大你知道吗,你能喊着保证全校都能听见么,这种办法肯定不行。那咋办,那就到学生中心xxx处,那里有台电脑,你去输入张三,它给你列出叫张三的人,里面不仅有姓名、性别、也有电话号码、家庭信息等,特别是头像,你就可以一眼认出这就是张三,然后最终你要获取的是啥?你是不是只想要他现在住哪个宿舍或者在哪个教室,不然你还要其他信息有啥用。
这个电脑就是哈希函数,这个相当于一个中间商,你不用管它如何处理的,只要它给你列出张三就行。
哈希函数有它独特的计算方式,感兴趣可以去了解Java中的HashMap。
关于他的读写操作,无法描述,需要自己去看,去体会,比较抽象,无法通俗的表述。你可以当做跟数组的类似去看去理解也行吧。
第三章 树
1.树
什么是树?就是外面的树(tree)啊,它有主干,分支,根错综复杂。最常见用树来表示的就是族谱或公司的层级等。简单的结果如下图:
看着比较简单,也有更复杂的关系图,不管怎么样这些的特点很明显,看着是不是跟树一样,有根、有枝干,有叶子。跟树一样,越大越复杂。但是从专业角度看:
从整体来看,它是有根节点,子树,叶子节点,,如下所示,要学会看,哪个是根节点(最顶端那个),哪个是子树(就是根节点外的都是根节点的孩子),哪个是叶子节点(就是最后一级的那些,说明就到它这代或者它就是最底层了);
从角度上看,有父节点、子节点/孩子节点/子孙节点、兄弟节点/.姐妹节点(叫法类似),要明白这些节点的意思,也要学会找到他们各自的关系,当然这里肯定是相对性的,要学会辨别,就如同你辨认你家亲戚这是谁谁谁一样。
也是要明白这些的定义,父节点(1是2的父节点,2是4或5的父节点..)、子孙节点(1的子节点是2, 1的孙节点是45, 1的曾孙节点是78,好像没有这种称呼,即1的子孙节点就是它下面的所有)、兄弟节点(2和3是兄弟节点,456是兄弟节点、78是兄弟节点)
在看看这个树一共几代?是不是4代,所以它的高度/深度就是4
是不是很好记?啊!?傻子才记。这多简单,所有专家命名还是有学问的。
B-树、B+树的区别:
B-树:就是B树,都是 B-tree 的翻译,里面不是减号-,是连接符-,所以就是树(也叫B树),有序数组+平衡多叉树;;
B+树:是一种树数据结构,常见于数据库与档案系统之中,有序数组链表+平衡多叉树
B+树能够使资料保持有序,并拥有均匀的对数处理时间的插入和删除动作。B树的元素通常会自底向上插入,有别于多数自顶向下插入的二叉树。
2.二叉树(重要)
这个二叉树到处可见,要把它理解透咯!设计内容较多。
什么是二叉树?所有说专门命名是有学问的,二叉树也是树啊,只是树的其中一种种类,二叉:顾名思义,就是每个节点最多只有2个孩子节点。注意:最多,语文水平高的理解下,最多是不是可以为1个也可以2个,就是不能3个,当然你也可以一个都没有,国家计划生育限制,悠着点,等待3胎政策可能就叫三叉树吧。
有2个孩子的,分为左右,叫左孩和右孩。
满二叉树
有些家庭很幸福美满,因为他们每个人都有后代,有子女,而且都是有两个子女(必须的,要满的),为国家做贡献。这种就叫满二叉树
完全二叉树
不是每个家庭都像上面那么满,子孙满堂,但是满二叉树有4代,那我家也有4代,我家就叫完全二叉树
很明显,一棵树,若为满二叉树,那么一定是完全二叉树。反之,不一定。
二叉树它属于逻辑结构,但它可以通过物理结构来表达,它的物理存储结构:
1.链式存储结构:一个节点含数据data,指向左孩子的left指针,指向右孩子的right指针。;
2.数组:按照层级顺序把二叉树的节点放到数组中对应的位置上。如果某一个节点的孩子为空,则数组的相应位置也空出来。所以对于稀疏的二叉树来说,用数组表示非常浪费空间的。那什么样的二叉树适合用数组表示,二叉堆,是一种特殊的完全二叉树,就是用数组存储的,以后会讲到;
2.1二叉树的应用
二叉树包含许多特殊的形式,每一种有自己的作用,主要应用在:二叉查找树和维持相对顺序
2.1.1二叉查找树(二叉排序树)
什么是二叉查找树?我们先来看一个标准的二叉查找树图:
看的出什么规律?它的官方定义如下,我们一一解读:
定义1.如果左子树不为空,则左子树上所有节点的值均小于根节点的值;
解读:左子树上就是如下图,他们的值都有比根节点6小,1234都是<6
定义2.如果右子树不为空,则右子树上所有节点的值均大于根节点的值;
解读:右子树上就是如下图,他们的值都要比6大,所以789都>6
定义3.左、右子树也都是二叉查找树。
解读:左右节点的二叉树也满足上面的要求,所以他们本身也是二叉查找树
二叉查找树的特点说完了,光看名字我们就知道它的作用就是查找,专家命名真是有学问。
书中的查找例子:
查找值为4的节点
1.如果访问根节点6,我们肯定知道4的节点肯定<根节点6,这是因为4是根节点的左子节点(看上面的定义1)
2.如果访问节6的左孩子节点3,那4肯定>3,因为4是3的右子节点(看上面的定义3+定义2)
3.如果访问3的有孩子节点4,那就是相等咯!
悟出来的结论:
看着这些感觉有啥意义?这么说吧,这个例子是你在已知它的值,觉得答案简单,如果没有值,你怎么判断大小?这还不是根据定义去判断,它在哪个位置,就知道它的大小。
就像这样,你来对比他们的大小。
我们知道二叉查找树要求:左子树<父节点,右子树>父节点,整数因为这样保证了二叉树的有序性。所以二叉查找树也叫二叉排序树
这个节点分布相对均衡的二叉查找树来说,如果总节点是n,那么它的时间复杂度就O(logn)--,这种依靠比较大小逐步查找的方式和二分查找法非常相似(也类似类似书中第一章11页例3,都是logn)
如果涉及到插入心得数呢,看你插在哪个地方,左边还是右边,上边还是下边,就类似上面的例子,你就知道值是多少了。
但是有个例外:
这种是会出现需要二叉树进行自平衡,时间复杂度从O(logn)就变为了O(n)。类似这种二叉树的自平衡的方式有很多种,如红黑树、AVL树、树堆等。
越到后面我就无法用自己的语言来描述了,因为数学这东西太抽象了,还是借鉴书上的,然后结合自己的理解,尽可能的通俗易懂。
2.2二叉堆
除二叉查找树外,二叉堆也维持着相对顺序。二叉堆条件相对宽松些,只要求父节点比它的左右孩子都大。
什么是二叉堆?--堆顶在堆中要么最大,要么最小,相当于当父亲的在家里的地位要么最大,要么最卑微,这就是二叉堆
本质上是一种完全二叉树分为最大堆和最小堆
最大堆:任何一个父节点的值,都大于或等于它左、右孩子节点的值;
最小堆:任何一个父节点的值,都小于或等于它左、右孩子节点的值。
二叉堆的根节点,叫作堆顶。最大堆的堆顶是整个堆中最大元素,最小堆的堆顶是整个堆中最小元素。
书中以最小堆为例:
你要插入节点,插入是完全二叉树的最后一个位置,那就要自我调整,要满足上面的理论,最大或最小,书中例子提到了,就是你插入的值不满足条件就要交互下位置。
你要删除节点,和插入节点过程正好相反,所删除的最处于堆顶的节点。
你要构建二叉堆,也就是把一个无序的完全二叉树调整为二叉堆。
二叉堆虽然是一个完全二叉树,但它的存储方式并不是链式存储,而是顺序存储,是实现堆排序及优先队列的基础,如下图所示:
二叉堆的插入删除时间复杂度为O(logn)
构建二叉堆,也就是把一个无序的完全二叉树调整为二叉堆,本质就是让所有非叶子节点一次“下沉”。时间复杂度O(n)
二叉堆学完了,我们就要实现优先队列了。
优先队列的实现
入队操作:
我们要使用的是二叉堆,比如我们要插入新节点5
很明显,二叉堆中的堆顶要么最小,要么最大,上面那个很明显是最大堆,所以5插进去很明显,5的父节点最小,不符合堆顶最大论,所以需要和它交换位置,就是叫上浮
如果是比8还大那就继续上浮,一次类推。
出队操作:
比如我们要删除堆顶10,它是皇帝老大,老大驾崩了。
然后缺少了最上层的堆顶,国不可一日无君,根据宪法规定,皇上去世,那就把最后的一个节点1来替代皇帝的位置,即日登基。
看着这二叉堆很明显,它本来是最大堆,堆顶都是最大的,也就是你让一个1岁小朋友继承皇位,那肯定没有办法服众嘛。所以老二9来替代,所以1就降职了,降下来比6还小,还会继续降,这就是下沉
所以他们的上浮和下沉的时间复杂度就是O(logn),入队和出队也是O(logn)。
2.3二叉树的遍历
在计算机程序中,遍历本身是一个线性操作,所以遍历同样具有线性结构的数组和链表是非常简单的。
但是二叉树是非线性数据结构,就比较复杂;
从节点之间位置关系的角度来看,我们可:前序遍历、中序遍历、后序遍历、层序遍历;
我们从更宏观的角度来看:我们有 深度优先遍历(前序遍历、中序遍历、后序遍历)、广度优先遍历(层序遍历);--这两个的概念不仅运用在二叉树,是一种抽象的思想。
我们先来看看这两种遍历的用法,先去玩比较简单的游乐场路线图,有这么一个树,你可以当它是游玩地图,这里的左右是对这个图而言:
2.3.1深度优先遍历DFS(Depth First Search)
什么是深度优先遍历?顾名思义,深度、优先关键字画重点嘛,就是纵深,一头扎到底,就是树的根往下延伸(深度不一定是向下,还可以向上的深度,就像树的枝叶)。也就是说往纵向优先,往底/顶遍历先,而不是往左右。他们的代表是:前序遍历、中序遍历、后序遍历。
2.3.1.1前序遍历
根据上面的游玩地图,你从顶点1开始,我们是深度优先,所以我们从1->2>4->5,然后到3->6这样的地图游玩整个景点。是这么玩?实际上你走路的路线你能走的通?比如4->5你是飞过去还是游过去?
我们从1->2没有问题,然后2->4没有问题,但是没有左孩也没有右孩(没路了),所以我们先返回2在到5,5没有左右孩子(就是没有路可走了),那么再回到2->1看看,然后再从另条路去3->6. 这样玩的遍历方法就是前序遍历。
顾名思义,就是我们从进入游乐场开始(路线有分叉口,我们先不管),一直走到底(记住遇到分叉口先选左边那条路),然后再返回,再走回原来没有走过的另条路,直到你把整个游乐场玩完,这就是前序遍历。
所以它的输出顺序(根据理解记忆):根节点开始、左子树、右子树
2.3.1.2中序遍历
再根据上面的游玩地图,我们从最底部开始,所以深度不一定是指向下延伸,还可以向上延伸,那我们从4开始,为什么从4开始?就是访问根节点的左孩子,如果这个用于做孩子的也拥有左孩子这就不算(比如2),我们这么理解,拥有左孩的地方门票太贵或者不好玩,我们先从最好玩的开始,然后在回去玩这些,左边左孩子没有拥有左孩的只能是4.其他的不符合。所以知道怎么找了吧,最简单的就是左边的最底。
我们开始遍历:从4->2->5,这里是不是会问为什么到5而不是1,刚刚说了,这个就是先访问没有左孩的优先,因为1还是有左孩。就先逛5,在去逛1,这样的玩法叫中序遍历,最后逛3->6.也就是我们玩的路线为:4->2->5->1->3->6
顾名思义,我网上遍历的时候先要把游乐场中心先玩了,什么是中心,就是没有左孩(门票贵又不好玩的地方,显然不是中心),我们是先玩门票便宜的中心,返回去玩非中心的地方,这就是中序遍历。
所以它的输出顺序(根据理解记忆):左子树、根节点、右子树
2.3.1.3后序遍历
这个跟前面两种思想不一样,这个书中没有细节,就直接列出了它的路线:4->5->2->6->3->6. 从这个路线不难看出,它的玩法是先把后面的,最尾部的先玩了,往往我们在娱乐场也不是只来一次,所以先把后面的玩了,然后如果天黑了,还没有玩完,下次来的时候就不用走最下面的点了,直接玩前面的,自己想象下,对吧!我们先来把它拆分成左右子树,那就左子树开始玩,第一个肯定是左边也就是4,它的最尾就是5了,玩完去2,这左子树就结束了,再去玩右子树最左边最底层开始,就是6,再到3在到1.
这种先拆分左右子树,先最左最底的思想玩法就是后续遍历
顾名思义,我们把游乐场分为两个区域(或者N个),左边是刺激恐怖区,右边是美食风景区,我们先选最左边的区域开始玩,然后先玩最后边(最里边,离出口最远)的那些,在玩靠近出口的那些,然后在去右边区域,也是从最后边开始再往出口方向游玩。
所以它的输出顺序(根据理解记忆):左子树、右子树、根节点
2.3.2广度优先遍历BFS(Breadth First Search)
什么是广度优先?恰恰与深度优先相反(书中有点废话了,肯定相反嘛,不然相同不就同一种方法了么),它就是在各个方向先走1不,在走下面的,用到的方法是层序遍历。说白了就是每层楼都逛完,在到下一层,跟你逛广场似的。
2.3.2.1层序遍历
这个名称很明显是跟楼层有关,一层层往下就是了。所以它的路线是1->2->3->4->5->6,从第一层开始先逛1,在到第二层逛2和3,在到第三层逛456,这从上到下从左到右,就叫层序遍历。
顾明思议,如果游乐场是楼层模式的,我们先从最顶层开始逛,逛完最上面的,在到下一层,在逛各个店,逛完后在到下一层,因为电梯在左边,所以你下去的时候也是从最左边的店开始逛(这就是为什么越靠近最左边,楼梯/电梯口、门口的租金最贵)。
所以它的输出顺序(根据理解记忆):按照从根节点到叶子节点的层级关系,一层一层横向遍历各个节点。
学完了深度优先遍历和广度优先遍历,那我们根据这两种来看看复杂的游乐场路线图怎么玩?
我们要知道这两种遍历的用法,比如下图是你在游乐场玩的地图景点,你现在在0这个这个景点怎么去玩呢?这个游乐场比较复杂。
第一种使用深度优先遍历方法(要结合地图看,纯看文字会蒙圈的):
1.在图中,我们首先选择景点1的这条路,继续深入到景点7、景点8,终于发现走不动了(景点旁边的数字代表探索次序):
2.于是,我们退回到景点7,然后探索景点10,又走到了死胡同。于是,退回到景点1,探索景点9:
3.按照这个思路,我们再退回到景点0,后续依次探索景点2、3、5、4、6,终于玩遍了整个游乐场:
像这样先深入探索,走到头再回退寻找其他出路的遍历方式,就叫做深度优先遍历的方法。(上面的方法就是前序遍历,你可以试试用中序和后续遍历试试)
第二种使用广度优先遍历方法:
1.首先把起点相邻的几个景点玩遍,然后去玩距离起点稍远一些(隔一层)的景点,然后再去玩距离起点更远一些(隔两层)的景点......
在图中,我们首先探索景点0的相邻景点1、2、3、4:
2.接着,我们探索与景点0相隔一层的景点7、9、5、6:
3.最后,我们探索与景点0相隔两层的景点8、10:
像这样一层一层由内而外的遍历方式,就叫做广度优先遍历(也就是把相邻的玩一遍,就是把离你最近的地方玩一遍,就跟层序遍历一样,把一个楼层玩完,就近原则)
从上面的案例不难看出,深度优先/广度优先如何实现?,我们在看看一个案例,这样更加深我们的理解(直接复制:漫画算法:深度优先遍历 和 广度优先遍历_weixin_30722589的博客-CSDN博客):
深度优先遍历实现
首先说说深度优先遍历的实现过程。这里所说的回溯是什么意思呢?回溯顾名思义,就是自后向前,追溯曾经走过的路径。
我们把刚才游乐场的例子抽象成数据结构的图,假如我们依次访问了顶点0、1、7、8,发现无路可走了,这时候我们要从顶点8退回到顶点7。
而后我们探索了顶点10,又无路可走了,这时候我们要从顶点10退回到顶点7,再退回到顶点1。
像这样的自后向前追溯曾经访问过的路径,就叫做回溯。
要想实现回溯,可以利用栈的先入后出特性,也可以采用递归的方式(因为递归本身就是基于方法调用栈来实现)。
下面我们来演示一下具体实现过程。
首先访问顶点0、1、7、8,这四个顶点依次入栈,此时顶点8是栈顶:
从顶点8退回到顶点7,顶点8出栈:
接下来访问顶点10,顶点10入栈:
从顶点10退到顶点7,从顶点7退到顶点1,顶点10和顶点7出栈:
探索顶点9,顶点9入栈:
以此类推,利用这样一个临时栈来实现回溯,最终遍历完所有顶点。
广度优先遍历实现
接下来该说说广度优先遍历的实现过程了。刚才所说的重放是什么意思呢?似乎听起来和回溯差不多?其实,回溯与重放是完全相反的过程(又废话了)。
仍然以刚才的图为例,按照广度优先遍历的思想,我们首先遍历顶点0,然后遍历了邻近顶点1、2、3、4:
接下来我们要遍历更外围的顶点,可是如何找到这些更外围的顶点呢?我们需要把刚才遍历过的顶点1、2、3、4按顺序重新回顾一遍,从顶点1发现邻近的顶点7、9;从顶点3发现邻近的顶点5、6。
像这样把遍历过的顶点按照之前的遍历顺序重新回顾,就叫做重放。同样的,要实现重放也需要额外的存储空间,可以利用队列的先入先出特性来实现。
下面我们来演示一下具体实现过程。
首先遍历起点顶点0,顶点0入队:
接下来顶点0出队,遍历顶点0的邻近顶点1、2、3、4,并且把它们入队:
然后顶点1出队,遍历顶点1的邻近顶点7、9,并且把它们入队:
然后顶点2出队,没有新的顶点可入队:
以此类推,利用这样一个队列来实现重放,最终遍历完所有顶点。
讲了这么多,发现没有,每种结构都是要去计算复杂度,这关乎于你以后写代码需要考虑的维度。大概统计下我们用到的复杂度:
基本 | 查找 | 更新 | 插入 | 删除 | 自平衡 | 构建 | 上浮/下沉 | 入队/出队 | |
数组 | O(1) | O(1) | O(n) | O(n) | |||||
链表 | O(n) | O(1) | O(1) | O(1) | |||||
栈 | O(1) | ||||||||
队列 | O(1) | ||||||||
优先队列 | O(logn) | ||||||||
二叉树 | |||||||||
二叉查找树 | O(logn) | O(n) | |||||||
二叉堆 | O(logn) | O(n) | O(logn) | ||||||
哈希表 | O(1) |
第四章 排序算法
终于等到这章节了,这才是进入算法的主题。
关于排序在生活中很常见,特别是购物网站,我们可以按照各种如价格进行排序。
排序用的好不好,就看你前面能领悟多少,也就是你自己想到用什么技巧,什么方法去实现,一般大佬已经给我们总结好了,根据时间的复杂度不同分为3类:
1.时间复杂度为O(n^2)的排序算法:冒泡排序、选择排序、插入排序、希尔排序(比较特殊,它的性能优越于O(n^2),单比不上O(nlogn),暂归在此处);
2.时间复杂度为O(nlogn)的排序算法:快速排序、归并排序、堆排序
3.时间复杂度为线性的排序算法:计数排序、桶排序、基数排序
排序还分为稳定排序和不稳定排序,我个人觉得没啥意思,大概意思就是说:一个10人队伍里他们是分开乱序的,其中有一对双胞胎也不排在一起(但是也是哥在前弟在后),进行排序后,双胞胎排在一起了,这时候双胞胎虽然在一起,还是原来哥在前弟在后,这个就是稳定排序,但是反过来弟在前哥在后(因为是双胞胎你不一定分得清谁是哥谁是弟),这种排序后跟原来不一样的就是不稳定排序。
书中有讲那些排序(冒泡是最典型的),这种理解起来比较抽象,也不好描述,我觉得网上找有那种动画的,视频的,好多种类型的,你一看就明白了。或者看:Java算法基础学习笔记_享有盛誉之名的博客-CSDN博客
这些一定要会用程序写出来,你背也好。经常考。要记住几个典型的,会描述出来即可。
第五章 面试中的算法
这章节说到我的痛点,比较我不是专业的程序员,我是属于测试的,面试中一考背下来还行,要是遇到大神面试官,人家题型一边,无法下手,所以最重要的还是理解,渗透的理解,融会贯通,代码要多写多练。就算 面试挂了,就当做历练,看看哪个地方不足,回来再学习,不要被面试官打击到。
5.1如何判断链表有环
5.2最小栈的实现
5.3如何求出最大公约数
5.4如何判定一个数是否为2的整数次幂
5.5无序数组排序后的最大相邻差
5.6如何用栈实现队列
5.7寻找全排列的下一个数
5.8删除K个数字后的最小值
5.9如何实现大整数相加
5.10如何求解基金矿问题
5.11寻找缺失的整数
第六章 算法的实际应用
-----------更新中-----敬请期待