链表性能对比

读取

我们曾经说过,当计算机要从数组中读取一个值时,它会一步跳到对应的格子上,其效率为O(1)。但在链表中就不是这样了。

假设程序要读取链表中索引2的值,计算机不可能在一步之内完成,因为无法一下子算出它在内存的哪个位置。毕竟,链表的结点可以分布在内存的任何地方。程序知道的只有第1个结点的内存地址,要找到索引2的结点(即第3个)​,程序必须先读取索引0的链,然后顺着该链去找索引1。接着再读取索引1的链,去找索引2,这才能读取到索引2里的值。

下面我们在LinkedList类中加入读取操作。

当想要读取某个索引时,可以这样写:

读取链表中某个索引值的最坏情况,应该是读取最后一个索引。这种情况下,因为计算机得从第一个结点开始,沿着链一直读到最后一个结点,于是需要N步。由于大O记法默认采用最坏情况,所以我们说读取链表的时间复杂度为O(N)。这跟读取数组的O(1)相比,的确是一大劣势。

查找

链表的查找效率跟数组一样。记住,所谓查找就是从列表中找出某个特定值所在的索引。对于数组和链表来说,它们都是从第一格开始逐个格子地找,直至找到。如果是最坏情况,即所找的值在列表末尾,或完全不在列表里,那就要花O(N)步。

下面是查找方法的实现。

有了它我们就可以这样来查找了:

插入

在某些情况下,链表的插入跟数组相比,有着明显的优势。回想插入数组的最坏情况:当插入位置为索引0时,因为需要先将插入位置右侧的数据都右移一格,所以会导致 O(N)的时间复杂度。然而,若是往链表的表头进行插入,则只需一步,即O(1)。下面看看为什么。

假设我们的链表如下所示。

要在表头增加"yellow",我们只需创建一个新的结点,然后使其链接到"blue"那一结点。

因为无须平移其他数据,所以与数组相比,链表在前端插入数据更为便捷。虽然理论上在链表的任何一处做插入都只需要1步,但事实上没那么简单。假设现在链表是这样的:

然后我们想在索引2("blue"和"green"之间)插入"purple"。由于插入动作创建了一个新的结点,如下图那样改动"blue"和"purple"的链,因此实际的操作只需1步。

但是,在该动作之前,计算机还得先找到索引1的结点("blue")​,让结点1的链指向新的结点。这个过程就是之前所说的读取链表,其效率为O(N)。下面我们来演示一下。

因为新结点是加在索引1之后,所以计算机要先找出索引1。这得从第一个结点开始。

接着通过第一个链访问下一个结点。

既然已到达索引1的结点,那就可以增加新的结点进去了。

刚才添加"purple"的例子花了3步。若想将它添加到链表的末尾,就得花5步:先是用4步跳到索引3上,再用1步插入新结点。

因此,链表的插入效率为O(N),与数组一样。

有趣的是,通过以上分析,你会发现链表的最坏情况和最好情况与数组刚好相反。在链表开头插入很方便,在数组开头插入却很麻烦;在数组的末尾插入是最好情况,在链表的末尾插入却是最坏情况。总结起来如下表所示。

下面给LinkedList类加上插入方法。

删除

从效率上来看,删除跟插入是相似的。如果删除的是链表的第一个结点,那就只要1步:将链表的first_node设置成当前的第二个结点。

回到"once"、"upon"、"a"和"time"的例子。如果要删除"once",那直接让链表以"upon"为开头就好了。

再回想删除数组的第一个元素时,得把剩余的所有元素左移一格,需要O(N)的时间复杂度。

删除链表的最后一个结点,其实际的删除动作只需1步——令倒数第二的结点的链指向null。然而,要找出倒数第二的结点,得花 N步,因为我们依然只能从第一个结点顺着链往下一个个地找。

下面这个表格对比了各种情况下数组和链表删除操作的效率。注意它跟插入效率的表格几乎一模一样。

要在链表中间做删除,计算机需要修改被删结点的前一结点的链,看下面的例子你就会明白。

假设现在要删除刚才例子的索引2的值("purple")​,计算机就会找出索引1的结点,将其链指向"green"结点。

LinkedList类的删除操作实现如下。

经过一番分析,链表与数组的性能对比如下所示。

尽管两者的查找、插入、删除的效率看起来差不多,但在读取方面,数组比链表要快得多。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值