Python 里面的链表与传统的 list 之间的差别

import time


def log_time(func, *args, **kwargs):
    def inner():
        t1 = time.time()
        func(*args, **kwargs)
        t2 = time.time()
        print(f"使用的时间是{t2 - t1}s")
    return inner


@log_time
def append_func():
    ll = list()
    for i in range(10000):
        ll.append(i)


@log_time
def insert_func():
    ll = list()
    for j in range(10000):
        ll.insert(0, j)


insert_func()
append_func()

通过以上程序说明 python 中 list 的 append 要快于 insert。

其实, Python 中的 list 并不是传统(计算机科学)意义上的列表。传统的列表,也叫做链表(Linked List),通常是由一系列的节点来实现的,其每个节点(尾节点除外)都有一个指向下一个节点的引用。
使用 python 实现一个链表的节点类如下:

class Node(object):
    def __init__(self, value, next=None):
        self.value = value
        self.next = next

接下来,我们就可以用其实现一个列表了:

# L 即一个链表的头节点
L = Node("a", Node("b", Node("c", Node("d"))))
print(L.value)
print(L.next.value)
print(L.next.next.value)
print(L.next.next.next.value)

这是一个所谓的单向列表,双向列表的各节点里面还有持有一个指向前一个节点的引用。但是 python 中的 list 则于此有点不同,它不是由若干个独立的节点相互引用形成的,而是一整块单一连续的内存块 —— 我们通常称之为数组(array)。这直接导致了它和链表之间的一些重要的区别。

如果我们想要按照既定的索引值对某元素进行直接访问的话,显然使用数组会更加有效率。因为在数组中,我们可以直接计算出目标元素在内存中的位置,并且能对其进行直接访问,而对于链表来说,我们必须从头开始访问整个链表。

而如果我们想要进行 insert 操作,对于链表来说,只要知道了在哪里执行 insert 操作,其操作成本是非常低的。无论其中有多少个元素,其操作时间大致上是相同的。而数组就非常不一样了,它在每次执行 insert 的时候都需要移动插入点右边的所有元素,甚至在必要时,我们还需要将这些列表元素整体搬到一个更大的数组中。也正是因为如此,append 操作通常采用一种被称为动态数组或者是向量的特定解决方案。其主要想法是将内存分配得大一点,并且等到其溢出时,在线性时间内再次重新分配内存。

这样做似乎会使得 append 和 insert 一样糟糕,其实不然,因为尽管这两种情况都有可能迫使我们去搬动大量的元素,但是主要的不同点在于,对于 append 操作,发生这样的可能性要小得多。事实上,如果我们能确保所搬入的数据都大过原来数据一定的比例(例如 20% 甚至 100%),那么该操作的平均成本(或者说得更加确切一些,将这些搬运开销均摊单每次 append 操作中去)通常是常数的。
我们可以认为,对于 python 中的 list 而言,append n 个数字所需要的运行时间是 O(n), 而在首端 insert 相同数量的数字需要的时间约为 O(n^2)。

更新时间: 2019.9.6
参考:《python 算法教程》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值