像数组一样,链表也用来表示一系列的元素。事实上,能用数组来做的事情,一般也可以用链表来做。然而,链表的实现跟数组是不一样的,在不同场景它们会有不同的性能表现。
计算机的内存就像一大堆格子,每格都可以用来保存比特形式的数据。当要创建数组时,程序会在内存中找出一组连续的空格子,给它们起个名字,以便你的应用存放数据,见下图。
计算机能够直接跳到数组的某一索引上。如果代码要求它读取索引4的值,那么计算机只需一步就可以完成任务。重申一次,之所以能够这样,是因为程序事先知道了数组开头所在的内存地址------例如地址是1000------当它想去索引4时,便会自动跳到1004处。
与数组不同的是,组成链表的格子不是连续的。它们可以分布在内存的各个地方。这种不相邻的格子,就叫作结点。
那么问题来了,计算机怎么知道这些分散的结点里,哪些属于这个链表,哪些属于其他链表呢?
这就是链表的关键了:每个结点除了保存数据,它还保存着链表里的下一结点的内存地址。这份用来指示下一结点的内存地址的额外数据,被称为链。链表如下图所示。
此例中,我们的链表包含4项数据:"a"、"b"、"c"和"d"。因为每个结点都需要2个格子,头一格用作数据存储,后一格用作指向下一结点的链(最后一个结点的链是null,因为它是终点),所以整体占用了8个格子。
若想使用链表,你只需知道第一个结点在内存的什么位置。因为每个结点都有指向下一结点的链,所以只要有给定的第一个结点,就可以用结点1的链找到结点2,再用结点2的链找到结点3......如此遍历链表的剩余部分。
链表相对于数组的一个好处就是,它可以将数据分散到内存各处,无须事先寻找连续的空格子。
实现一个链表
我们用Ruby来写一个链表,最终实现包含两个类:Node和LinkedList。先是Node。
Node类有两个属性:data表示结点所保存的数据,next_node表示指向下一结点的链,使用方法如下。
以上代码创建了4个连起来的结点,它们分别保存着"once"、"upon"、"a"和"time" 4项数据。虽然只用Node也可以创建出链表,但我们的程序无法由此轻易地得知哪个结点是链表的开端。因此我们还得创建一个LinkedList类。下面是一个最基本的LinkedList的写法。
有了这个类,我们就可以用以下代码让程序知道链表的起始位置了。
LinkedList的作用就是一个指针,它指向链表的第一个结点。
既然知道了链表是什么,下一篇文章会写它跟数组的性能对比,观察它们在读取、查找、插入和删除上有何优劣。