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 算法教程》