Python 十大数据结构

1.list

  • 基本用法
  • 使用场景
    list 使用在需要查询,修改的场景,极不擅长需要频繁的插入,删除元素的场景
  • 实现原理
    list 对应数据结构的线性表,列表长度在初始状态无需指定,当插入元素超过初始长度后在启动动态扩容,时间复杂度O(n)

2.tuple

元组是一类不允许添加和删除元素的特殊列表,一旦创建不允许添加和删除修改

  • 基本用法
    元组大量使用在打包和解包处,如函数有多个返回值打包为一个元组,赋值到等号左侧变量时解包
t = 1,2,3
type(t)
tuple
  • 使用场景
    相比于list,tuple实例更加节省内存,如果确定你的对象后面不会被修改,可以大胆使用元组。
# getsizeof 获取对象所占内存
from sys import getsizeof
getsizeof(list())#56
getsizeof(tuple())#40

不同Python 版本得到的值可能不一样,我测试版本为Python3.9

  • 实现原理

3.set

  • 基本用法
    set 是一种里面不能含有重复元素的数据结构,这种特性可以用来列表的去重
# 使用set 对list 去重
a = [1,2,3,4,5,1]
set(a)# {1,2,3,4,5}
# 使用 set 对set 做交集,并集,差集等操作
a = {1,2,3}
b = {3,4,5}
a.intersection(b) #{3}
  • 使用场景
    如果只是想缓存某些元素值,并且要求元素值不能重复时,可以使用此结构,并且set内部允许增删元素,且效率很高
  • 实现原理
    set 在内部将值哈希为索引,然后按照索引去获取数据,因此删除,增加,查询元素效率都很高

4.dict

  • 基本用法
# 创建字典
d = {'a':1,'b':2}
# 列表生成式
d = {a:b for a,b in zip(['a','b'],[1,2])}
d #{'a':1,'b':2}
  • 使用场景
    字典适合在查询较多的场景,时间复杂度O(1),Python类中属性值等信息也是缓存在__dict__这个字典型数据结构中
from sys import getsizeof
getsizeof(dict()) #232

dict占用字节数是list,tuple 的三四倍,对内存要求苛刻的场景谨慎使用字典

  • 实现原理
    字典是一种哈希表,同时保存了键值对

5.deque

deque 双端队列,基于list 优化了列表两端的增删数据的操作

  • 基本用法
from collections import deque
d = deque([3,2,4,0])
# 左侧移除元素 O(1) 时间复杂度
d.popleft() #3
# 左侧添加元素O(1)时间复杂度
d.appendleft(3) #3
d #deque([3,2,4,0])
  • 使用场景
    list 左侧添加删除元素的时间复杂度都为O(n),所以在Python中模拟队列是不要使用list,deque双端队列非常适合频繁在列表两端操作的场景,但是deque占用字节数特别大
In [15]: from sys import getsizeof
In [16]: from collections import deque
In [17]: getsizeof(deque)
Out[17]: 408
  • 实现原理
    cpython 实现deque使用默认长度64的数组,每次从左侧移除一个元素,leftindex 加1,如果超过64就释放原来的内存块,在重新申请64长度的数组,并使用双端链表block管理内存块。

6.Counter

Counter 一种继承于dict用于统计元素个数的数据结构,也被称为bag或multiset

  • 基本用法
In [18]: from collections import Counter
In [19]: c = Counter([1,2,3,4,5,1,2,3])
In [20]: c
Out[20]: Counter({1: 2, 2: 2, 3: 2, 4: 1, 5: 1})
# 统计第一最常见的项,返回元素及其次数的元组
In [21]: c.most_common(1)
Out[21]: [(1, 2)]
  • 使用场景
    基本的dict能解决的问题就不要用Counter,但是遇到统计元素出现频次的场景,果断使用Counter
  • 实现原理
    Counter实现基于dict,它将元素存储于keys上,出现次数为values

7.OrderedDict

  • 基本用法
    继承于dict,能确保keys值按照顺序取出来的数据结构
In [22]: from collections import OrderedDict

In [23]: od = OrderedDict({'c':3,'a':1,'b':2})

In [24]: for k,v in od.items():
    ...:     print(k,v)
    ...:
c 3
a 1
b 2
  • 使用场景
    基本的dict无法保证顺序,keys映射为哈希值,而此值不是按照顺序存储在散列表中,所以遇到要确保字典keys有序场景,就要使用OrderedDict
  • 实现原理
    你一定会好奇OrderedDict如何确保keys顺序的,翻看cpython看到它里面维护着一个
    双向链表self.__root ,它维护着keys的顺序。既然使用双向链表,细心的读者可能会有疑
    问:删除键值对如何保证O(1)时间完成?
    cpython使用空间换取时间的做法,内部维护一个self.__map 字典,键为key,值为指向双向链
    表节点的link . 这样在删除某个键值对时,通过__map在O(1)内找到link,然后O(1)内从双向链
    表__root中摘除。

8.heapq

基于list优化的一个数据结构:堆队列,也称为优先队列。堆队列特点在于最小的元素
总是在根结点

  • 基本用法
In [25]: import heapq
In [26]: a = [2,3,4,15,1]
# 对 a 建堆,建堆完成后对a就地排序
In [27]: heapq.heapify(a)
# 排序好的 a
In [28]: a
Out[28]: [1, 2, 4, 15, 3]
In [29]: a[0]
Out[29]: 1
# 最大的前两个元素
In [30]: heapq.nlargest(2,a)
Out[30]: [15, 4]
# 最小的前三个元素
In [31]: heapq.nsmallest(3,a)
Out[31]: [1, 2, 3]
  • 使用场景
    如果要想要统计list中前几个最小(大)元素,使用heapq很方便,同时它还提供合并多个有序小list为大list的功能
  • 实现原理
    堆是一个二叉树,它的每个父节点的值都只会小于或大于所有的孩子节点的值

9.defaultdict

  • 基本用法
In [34]: words=['book','nice','great','book']
In [35]: d ={}
In [36]: for i,word in enumerate(words):
    ...:     if word in d:
    ...:         d[word].append(i)
    ...:     else:
    ...:         d[word] =[i]
    ...:

In [37]: d
Out[37]: {'book': [0, 3], 'nice': [1], 'great': [2]}
# 使用defaultdict
In [38]: from collections import defaultdict
In [39]: d = defaultdict(list)
In [40]: for i,word in enumerate(words):
    ...:     d[word]=i
    ...:
In [41]: d
Out[41]: defaultdict(list, {'book': 3, 'nice': 1, 'great': 2})
  • 使用场景
    适用于键的值必须指定一个默认值的场景,如键的值为list,set,dict
  • 实现原理
    调用工厂函数去提供确实的键的值

10.ChainMap

  • 基本用法
    如果有多个dict 想要合并成一个大的dict,那么ChainMap是你的选择,它的方便性体现在同步更改。
In [42]: from collections import ChainMap

In [43]: d1 = {'a':1,'b':2,'c':3}

In [44]: d2 = {'d':4,'e':5,'f':6}
# 使用 ChainMap合并 d1 d2
In [45]: dm = ChainMap(d1,d2)
In [46]: dm
Out[46]: ChainMap({'a': 1, 'b': 2, 'c': 3}, {'d': 4, 'e': 5, 'f': 6})
In [48]: dm.maps
Out[48]: [{'a': 1, 'b': 2, 'c': 3}, {'d': 4, 'e': 5, 'f': 6}]
# 增加元素,体现在d1上
In [49]: dm.maps[0][2]=9
In [50]: dm.maps
Out[50]: [{'a': 1, 'b': 2, 'c': 3, 2: 9}, {'d': 4, 'e': 5, 'f': 6}]
In [51]: d1
Out[51]: {'a': 1, 'b': 2, 'c': 3, 2: 9}
# 修改元素,体现在d1上
In [52]: dm.maps[0]['c']=9
In [53]: dm
Out[53]: ChainMap({'a': 1, 'b': 2, 'c': 9, 2: 9}, {'d': 4, 'e': 5, 'f': 6})
In [54]: d1
Out[54]: {'a': 1, 'b': 2, 'c': 9, 2: 9}
  • 使用场景
    具体使用场景是我们有多个字典或者映射,想把他们合并成为一个单独的映射

使用update进行合并,会新建一个内存结构,除了浪费空间外,还有一个缺点,就是我们对新字典的更改不会同步到源字典上

  • 实现原理
    通过maps便能观察出ChainMap联合多个小dict装入list中,实际确实也是这样实现的,内部维护一个lis实例,其元素为小dict.