《个人第一本算法书》根据 iOS 和 Android 平台上的应用程序“算法动画图解”编写而成,为配合图书出版,对内容进行了补充和修正,专门添加了基础理论方面的内容。算法
数据存储于计算机的内存中。内存如右图所示,形似排成 1 列的箱子,1 个箱子里存储 1 个数据。数组
数据存储于内存时,决定了数据顺序和位置关系的即是“数据结构”。数据结构
举个简单的例子。假设咱们有 1 个电话簿——虽然说如今不少人都把电话号码存在手机里,可是这里咱们考虑使用纸质电话簿的状况——每当咱们获得了新的电话号码,就按从上往下的顺序把它们记在电话簿上。学习
假设此时咱们想给“张伟”打电话,可是由于数据都是按获取顺序排列的,因此咱们并不知道张伟的号码具体在哪里,只能从头一个个往下找(虽然说也能够“从后往前找”或者“随机查找”,可是效率并不会比“从上往下找”高)。若是电话簿上号码很少的话很快就能找到,但若是存了 500 个号码,找起来就不那么容易了。动画
接下来,试试以联系人姓名的拼音顺序排列吧。由于数据都是以字典顺序排列的,因此它们是有“结构”的。3d
使用这种方式给联系人排序的话,想要找到目标人物就轻松多了。经过姓名的拼音首字母就能推测出该数据的大体位置。指针
那么,如何往这个按拼音顺序排列的电话簿里添加数据呢?假设咱们认识了新朋友“柯津博”并拿到了他的电话号码,打算把号码记到电话簿中。因为数据按姓名的拼音顺序排列,因此柯津博必须写在韩宏宇和李希之间,可是上面的这张表里已经没有空位可供填写,因此须要把李希及其如下的数据往下移 1 行。cdn
此时咱们须要从下往上执行“将本行的内容写进下一行,而后清除本行内容”的操做。若是一共有 500 个数据,一次操做须要 10 秒,那么 1 个小时也完成不了这项工做。blog
总的来讲,数据按获取顺序排列的话,虽然添加数据很是简单,只须要把数据加在最后就能够了,可是在查询时较为麻烦;以拼音顺序来排列的话,虽然在查询上较为简单,可是添加数据时又会比较麻烦。排序
虽然说这两种方法各有各的优缺点,但具体选择哪一种仍是要取决于这个电话簿的用法。若是电话簿作好以后就再也不添加新号码,那么选择后者更为合适;若是须要常常添加新号码,但不怎么须要再查询,就应该选择前者。
咱们还能够考虑一种新的排列方法,将两者的优势结合起来。那就是分别使用不一样的表存储不一样的拼音首字母,好比表 L、表 M、表 N 等,而后将同一张表中的数据按获取顺序进行排列。
这样一来,在添加新数据时,直接将数据加入到相应表中的末尾就能够了,而查询数据时,也只须要到其对应的表中去查找便可。
由于各个表中存储的数据依旧是没有规律的,因此查询时仍需从表头开始找起,但比查询整个电话簿来讲仍是要轻松多了。
数据结构方面的思路也和制做电话簿时的同样。将数据存储于内存时,根据使用目的选择合适的数据结构,能够提升内存的利用率。
本章将会讲解 7 种数据结构。如本节开头所述,数据在内存中是呈线性排列的,可是咱们也可使用指针等道具,构造出相似“树形”的复杂结构(树形结构将在 4-2 节详细说明)。
参考:4-2 广度优先搜索
链表是数据结构之一,其中的数据呈线性排列。在链表中,数据的添加和删除都较为方便,就是访问比较耗费时间。
对链表的操做所需的运行时间究竟是多少呢?在这里,咱们把链表中的数据量记成n。访问数据时,咱们须要从链表头部开始查找(线性查找),若是目标数据在链表最后的话,须要的时间就是 O(n)。
另外,添加数据只须要更改两个指针的指向,因此耗费的时间与 n 无关。若是已经到达了添加数据的位置,那么添加操做只需花费 O(1) 的时间。删除数据一样也只需O(1) 的时间。
参考:3-1 线性查找
上文中讲述的链表是最基本的一种链表。除此以外,还存在几种扩展方便的链表。
虽然上文中提到的链表在尾部没有指针,但咱们也能够在链表尾部使用指针,而且让它指向链表头部的数据,将链表变成环形。这即是“循环链表”,也叫“环形链表”。循环链表没有头和尾的概念。想要保存数量固定的最新数据时一般会使用这种链表。
另外,上文链表里的每一个数据都只有一个指针,但咱们能够把指针设定为两个,而且让它们分别指向先后数据,这就是“双向链表”。使用这种链表,不只能够从前日后,还能够从后往前遍历数据,十分方便。
可是,双向链表存在两个缺点:一是指针数的增长会致使存储空间需求增长;二是添加和删除数据时须要改变动多指针的指向。
数组也是数据呈线性排列的一种数据结构。与前一节中的链表不一样,在数组中,访问数据十分简单,而添加和删除数据比较耗工夫。这和 1-1 节中讲到的姓名按拼音顺序排列的电话簿相似。
参考:1-1 什么是数据结构
这里讲解一下对数组操做所花费的运行时间。假设数组中有 n 个数据,因为访问数据时使用的是随机访问(经过下标可计算出内存地址),因此须要的运行时间仅为恒定的O(1)。
但另外一方面,想要向数组中添加新数据时,必须把目标位置后面的数据一个个移开。因此,若是在数组头部添加数据,就须要 O(n) 的时间。删除操做同理。
在链表和数组中,数据都是线性地排成一列。在链表中访问数据较为复杂,添加和删除数据较为简单;而在数组中访问数据比较简单,添加和删除数据却比较复杂。
咱们能够根据哪一种操做较为频繁来决定使用哪一种数据结构。
栈也是一种数据呈线性排列的数据结构,不过在这种结构中,咱们只能访问最新添加的数据。栈就像是一摞书,拿到新书时咱们会把它放在书堆的最上面,取书时也只能从最上面的新书开始取。
像栈这种最后添加的数据最早被取出,即“后进先出”的结构,咱们称为 Last In First Out,简称 LIFO。
与链表和数组同样,栈的数据也是线性排列,但在栈中,添加和删除数据的操做只能在一端进行,访问数据也只能访问到顶端的数据。想要访问中间的数据时,就必须经过出栈操做将目标数据移到栈顶才行。
栈只能在一端操做这一点看起来彷佛十分不便,但在只须要访问最新数据时,使用它就比较方便了。
好比,规定(AB(C(DE)F)(G((H)I J)K))这一串字符中括号的处理方式以下:首先从左边开始读取字符,读到左括号就将其入栈,读到右括号就将栈顶的左括号出栈。此时,出栈的左括号便与当前读取的右括号相匹配。经过这种处理方式,咱们就能得知配对括号的具体位置。
另外,咱们将要在 4-3 节中学习的深度优先搜索算法,一般会选择最新的数据做为候补顶点。在候补顶点的管理上就可使用栈。
参考:4-3 深度优先搜索