为什么写这个,我在leetcode刷题(leetcode 707 设计链表 Design Linked List),有个设计链表的题,还特意加了尾指针,也试过双链表,结果时间上还是跑不过别人的直接用list去append和insert的方案,所以随便测一下动态数组list的实现机制。只测了个大概,可能有不严谨的地方或者了解不全面的地方,欢迎指正。
目录
从头部删除一个元素,再从头部插入一个元素,是维持原来的内存占用吗?
最骚的操作是,他从来不整体复制,新增的部分确实会开辟新空间,但是原来的元素是不会轻易移动的。
结论:python list高度封装,你能想到的点他基本都处理掉了,建立了访问映射机制,内存分布无论多乱都不影响外部访问。
用时对比图
python动态数组list机制探索
如果size不超,难道就不重新分配空间了吗?
"动态数组的机制是分配一块固定capacity的空间,如果size超出capacity就会重新分配一块更大capacity的空间",死知识是这样的。实际上呢,肯定不是!!!
下面就是一个size一直不超过2,但是内存重新分配的例子。
刚开始应该是分配到栈空间了。加一个元素,删一个元素,动态数组的实现机制,肯定是后延的,最后超出了初始指定的范围以后,直接去堆空间重新划分了一块。
l = [0]
for i in range(1,1000):
print(l)
# print(id(l))
print(id(l[0]))
l.append(i)
l.remove(i-1)
所以尾插,删头部,size不增长,和正常使用size增长超过capacity是一个效果。
从头部删除一个元素,再从头部插入一个元素,是维持原来的内存占用吗?
l = [1,2]
print(l)
print(id(l[0]))
print(id(l[1]))
l.remove(1)
print(l)
print(id(l[0]))
l.insert(0,5)
print(l)
print(id(l[0]))
print(id(l[1]))
并不是,新的index-0是xxx456,比原来最大的xxx360还大,关键是index-1还维持原来地址,并不同步复制到后边。可以看出,动态数组的实现为了性能的考量,基本上,能不复制能不移动,都是不复制不移动的。
最骚的操作是,他从来不整体复制,新增的部分确实会开辟新空间,但是原来的元素是不会轻易移动的。
l = [-4,-3,-2,0]
for i in range(1,1000):
print(l)
# print(id(l))
l.append(i)
print(id(l[0]),id(l[1]),id(l[2]),id(l[3]))
l.remove(i-1)
还有删除操作,就算把地址空出来也不会有额外变动
l = [-4,-3,-2,0]
print(id(l[0]),id(l[1]),id(l[2]),id(l[3]))
l.remove(-2)
print(id(l[0]),id(l[1]),id(l[2]))
==============================================================================================================================================================================================
结论:python list高度封装,你能想到的点他基本都处理掉了,建立了访问映射机制,内存分布无论多乱都不影响外部访问。
假链表所有方面都不输,完全没有多余的复制操作,头尾插入和删除全是O(1),删除指定index也是O(1),而真链表只能O(n)。
所以真链表很难比用动态数组实现的假链表快。不是很难,是不能!吧?