前言
之前写了一篇《没有指针,Python如何实现链表、二叉树这些数据结构》然后有大佬觉得我那里面讲的是列表,不是链表。当时我也很疑惑,因为我也不太确定列表和链表到底有什么区别,说实话,列表确实比较好用,因为是Python已经封装好了的,方法种类多样,更加的实用。但是这难道就意味着链表这一数据结构没有丝毫的用处嘛?当然不是,只是可能你还没有到非用他不可的地步罢了。
列表详解
列表的实现机制
Python标准类型list就是一种元素个数可变的线性表,可以加入和删除元素,并在各种操作中维持已有元素的顺序(即保序),而且还具有以下行为特征:
基于下标(位置)的高效元素访问和更新,时间复杂度应该是O(1);
为满足该特征,应该采用顺序表技术,表中元素保存在一块连续的存储区中。
允许任意加入元素,而且在不断加入元素的过程中,表对象的标识(函数id得到的值)不变。
为满足该特征,就必须能更换元素存储区,并且为保证更换存储区时list对象的标识id不变,只能采用分离式实现技术。
在Python的官方实现中,list就是一种采用分离式技术实现的动态顺序表。这就是为什么用list.append(x) (或 list.insert(len(list), x),即尾部插入)比在指定位置插入元素效率高的原因。
在Python的官方实现中,list实现采用了如下的策略:在建立空表(或者很小的表)时,系统分配一块能容纳8个元素的存储区;在执行插入操作(insert或append)时,如果元素存储区满就换一块4倍大的存储区。但如果此时的表已经很大(目前的阀值为50000),则改变策略,采用加一倍的方法。引入这种改变策略的方式,是为了避免出现过多空闲的存储位置。
翻阅了多方的资料,看了其他一些大佬发的东西,我在这里总结一下:
列表的实现可以是数组或者链表。并且通过前面的学习我们知道,列表是一种顺序表,顺序表一般是数组。列表是一个线性表,它允许用户在任何位置进行插入、删除、访问和替换元素。
列表的实现是基于数组或者基于链表结构,当使用列表迭代器的时候,双向链表结构比单链表结构更快。
Python中的列表英文名是list,因此很容易与C语言中的链表搞混了,因为在C语言中大家经常给链表命名为list。事实上CPython(CPython是指用C语言实现的Python,也是我们常见的用C语言开发的Python解释器,大家应该都知道,Python语言底层是C语言实现的)中的列表根本不是列表。在CPython中列表被实现为长度可变的数组。
从细节上看,Python中的列表是由其他对象的引用组成的连续数组,指向这个数组的指针及其长度被保存在一个列表头结构中。这就意味着,每次添加或删除一个元素时,由引用组成的数组需要改变大小(重新分配)。幸运的是,Python在创建这些数组时采用了指数分配,所以并不是每次操作都要改变数组的大小。但是,也因为这个原因添加或者取出元素是平摊复杂度较低。不幸的是,在普通链表上“代价很小”的其他一些操作在Python中计算复杂度相对较高。
总的来说,Python中的列表是一个动态的顺序表,而顺序表大多是由数组实现的。
链表
Python链表的具体实现在我上面那篇文章里面介绍过了。我这里就再来炒个剩饭。
链表是由许多相同数据类型的数据项按照特定的顺序排列而成的线性表。链表中的数据项咋计算机的内存中的位置是不连续且随机的,然而列表是连续的。链表数据的插入和删除是很方便的,但数据的查找效率较低,不能像列表一样随机读取数据。
链表由一个一个的结点构成,每个结点由数据域和“指针域”组成,数据域存储数字,“指针域”指向下一个结点所在的内存地址。(因为Python中没有指针这一概念的,这里的指针是一种指向)
class Node(object):
"""节点"""
def __init__(self, elem):
self.elem = elem
self.next = None
链表封装的一系列操作:
cl