【数据结构】python常见的数据结构

1、有序集合

顾名思义,就是按顺序排列的集合(列表、字典),增删元素都会保持原集合的有序性。

from sortedcontainers import SortedList

# 声明有序集合
list1 = SortedList([1,8,6,5,2,3,4])   
> SortedList([1, 2, 3, 4, 5, 6, 8])

# 添加元素值
list1.add(2)            
> SortedList([1, 2, 2, 3, 4, 5, 6, 8])

# 删除元素,参数为具体的元素,而不是下标,
list1.remove(4)         
> SortedList([1, 2, 2, 3, 5, 6, 8])
# 如果不存在则报错ValueError
list1.remove(12)
> ValueError: 12 not in list

# 删除元素,参数为具体的元素,而不是下标,
# 如果不存在不报错
list1.discard(12)       
> SortedList([1, 2, 2, 3, 5, 6, 8])

# 删除元素,参数为下标
list1.pop(0)            
> SortedList([2, 2, 3, 5, 6, 8])

# 查找元素可以插入的位置下标,如果这个值已经存在,
# 则插入已经存在的所有 values 之前
index1 = list1.bisect_left(2)   
> 0

# 查找元素可以插入的位置下标,如果这个值已经存在,
# 则插入已经存在的所有values之后
index2 = list1.bisect_right(2)   
> 2

此外,有序集合和有序字典分别为

from sortedcontainers import SortedSet
from sortedcontainers import SortedDict

2、collections

defaultdict

当改变字典中的某个key的value时,需要先判断key是否存在,不存在需要先建立一对键值对,采用下面的写法可以省略这步,可以用来统计数量,比如统计文本词频

from collections import defaultdict

s = [1,2,3,3,2,6,4,3,2,4,5]
count = defaultdict(int)
for ch in s:
    count[ch] += 1
> defaultdict(<class 'int'>, {
    
    1: 1, 2: 3, 3: 3, 6: 1, 4: 2, 5: 1})

names = ['Job','Ben','Job','Job','Job','Tim','Ken','Ken']
count2 = defaultdict(int)
for name in names:
    count2[name] += 1
> defaultdict(<class 'int'>, {
    
    'Job': 4, 'Ben': 1, 'Tim': 1, 'Ken': 2})

Counter

功能同上,统计字符串、字典、元组、列表、集合中的词频,返回一个字典

from collections import Counter

names = ['Job','Ben','Job','Job','Job','Tim','Ken','Ken']
counter = Counter(names)  # 返回类型为<class 'collections.Counter'>
> Counter({
    
    'Job': 4, 'Ken': 2, 'Ben': 1, 'Tim': 1})
# 可以以字典的方式提取键值对
for k, v in counter.items():
    print(k, v)
> Job 4
> Ben 1
> Tim 1
> Ken 2

# 返回出现次数最多的N个数及其出现的次数
ansdict = counter.most_common(3)  # 类型为<class 'list'>
> [('Job', 4), ('Ken', 2), ('Ben', 1)]

3、二分查找

用于维护有序列表
bisect.bisectbisect.bisect_right返回 大于 x的 最右侧 下标;
bisect.bisect_left返回 大于等于 x的 最左侧 下标,如果存在元素==x,则返回第一个相同元素的下标

bisect.bisect

import bisect
#第1个参数是列表,第2个参数是要查找的数,返回值为索引, lo= 和hi= 用于指定查找区间
index1 = bisect.bisect(ls, x, lo=, hi=)   
index2 = bisect.bisec_right(ls, x, lo=, hi=)  # 用法同bisect()
index3 = bisect.bisect_left(ls, x, lo=, hi=)

几个例子:

  • 如果列表中没有元素x,那么bisect_left(ls, x)和bisec_right(ls, x)返回相同的值,该值是x在ls中“合适的插入点索引,使得数组有序”。此时,ls[index2] > x,ls[index3] > x
import bisect
# i :[0,1,2,3, 4]
ls = [1,5,9,13,17]
index1 = bisect.bisect(ls,7)
index2 = bisect.bisect_left(ls,7)
index3 = bisect.bisect_right(ls,7)

> index1 = 2, index2 = 2, index3 = 2
  • 如果列表中只有一个元素等于x,那么bisect_left(ls, x)的值是x在ls中的索引,ls[index2] = x。而bisec_right(ls, x)的值是x在ls中的索引加1,ls[index3] > x
# i :[0,1,2,3, 4]
ls = [1,5,9,13,17]
index1 = bisect.bisect(ls,9)
index2 = bisect.bisect_left(ls,9)
index3 = bisect.bisect_right(ls,9)

> index1 = 3, index2 = 2, index3 = 3
  • 如果列表中存在多个元素等于x,那么bisect_left(ls, x)返回最左边的那个索引,此时ls[index2] = x。bisect_right(ls, x)返回最右边的那个索引加1,此时ls[index3] > x
# i :[0,1,2,3,4]
ls = [1,5,5,5,17]
index1 = bisect.bisect(ls,5)
index2 = bisect.bisect_left(ls,5)
index3 = bisect.bisect_right(ls,5)
print("index1 = {}, index2 = {}, index3 = {}".format(index1, index2, index3))

> index1 = 4, index2 = 1, index3 = 4

numpy.searchsorted

import numpy as np
index1 = np.searchsorted(ls, x) # 与bisect_left()类似
index2 = np.searchsorted(ls, x, side='right') # 与bisect()类似

# 还可以同时找多个数
index_array = np.searchsorted(ls, [x1,x2,x3])

list上查找效率不如bisect.bisect;
numpy.ndarray上效率比bisect.bisect快很多

bisect.insort

bisect用来找到插入位置,但并不插入;
insort既找位置,又插入,函数作用与ls.insert(bisect.bisect(ls, x,lo=, hi=), x)一样。

import bisect
#第1个参数是列表,第2个参数是要查找的数,返回值为索引, lo= 和hi= 用于指定查找区间
bisect.insort(ls, x,lo=, hi=)
bisect.insort_right(ls,x, lo=, hi=)
bisect.insort_left(ls,x, lo=, hi=)

本节内容参考http://t.csdn.cn/IPTsf,以及http://t.csdn.cn/QQprX

扫描二维码关注公众号,回复: 16427762 查看本文章

4、堆

堆是一种基本的数据结构,堆的结构是一棵完全二叉树,并且满足堆积的性质:每个节点(叶节点除外)的值都大于等于(或都小于等于)它的子节点

堆结构分为大顶堆和小顶堆,在heapq中使用的是小顶堆:

  1. 大顶堆:每个节点(叶节点除外)的值都大于等于其子节点的值,根节点的值是所有节点中最大的。
  2. 小顶堆:每个节点(叶节点除外)的值都小于等于其子节点的值,根节点的值是所有节点中最小的。

在heapq库中,heapq使用的数据类型是Python的基本数据类型 list ,要满足堆积的性质,则在这个列表中,索引 k 的值要小于等于索引 2k+1 的值和索引 2k+2 的值(在完全二叉树中,将数据按广度优先插入,索引为k的节点的子节点索引分别为2k+1和2k+2)

创建堆

有两种方法:

  1. 创建空堆,一个一个加到堆中。 heapq.heappush(list, num)
  2. 直接用list构造堆 heapq.heapify(list)

heappush的时间复杂度是 O(logN)
heapify的时间复杂度是 O(NlogN)

两种实现方式都满足小顶堆的定义,并且类型都是list。

import heapq
array = [10, 17, 50, 7, 30, 24, 27, 45, 15, 5, 36, 21]
heap = []
# 方法一:
# heappush(heap, num),先创建一个空堆,然后将数据一个一个地添加到堆中。每添加一个数据后,heap都满足小顶堆的特性。
for num in array:
    heapq.heappush(heap, num)
> heap:  [5, 7, 21, 15, 10, 24, 27, 45, 17, 30, 36, 50]

# 方法二:
# heapify(array),直接将数据列表调整成一个小顶堆,原来的list会被改变
heapq.heapify(array)
> array:  [5, 7, 21, 10, 17, 24, 27, 45, 15, 30, 36, 50]

堆排序

相当于从小堆顶每次取出堆顶元素,构成list,实现升序排序
每次取出堆顶元素,堆会被重新排列,保证满足小堆顶定义。
heappop的时间复杂度是 log(k)

array = [10, 17, 50, 7, 30, 24, 27, 45, 15, 5, 36, 21]
heap = []
for num in array:
    heapq.heappush(heap, num)
heap_sort = [heapq.heappop(heap) for _ in range(len(heap))]

> heap_sort:  [5, 7, 10, 15, 17, 21, 24, 27, 30, 36, 45, 50]

堆排序的时间复杂度是O(nlogn)
快排的时间复杂度是O(nlogn),但是最坏情况是O(n^2)

获取堆中前k个最小值和最大值

max_k_list = nlargest(k, heap),从堆中取出K个数据,从最大的数据开始取,返回结果是一个列表(即使只取一个数据)
min_k_list = nsmallest(k, heap),从堆中取出num个数据,从最小的数据开始取,返回结果是一个列表。
直接sort排序的时间复杂度是O(nlogn),而用堆来实现上是,建堆O(k)+每次的堆调整O((n-k)logk),就是O(k+(n-k)logk)。用堆来实现topk问题还有一个优势就是面对大量数据可以分批拉到内存中,不用担心内存不够用。

array = [10, 17, 50, 7, 30, 24, 27, 45, 15, 5, 36, 21]
heapq.heapify(array)
print(heapq.nlargest(2, array))
> [50, 45]
print(heapq.nsmallest(3, array))
> [5, 7, 10]

如果K大于等于堆中的数据数量,则从大到小取出堆中的所有数据,不会报错,相当于实现了降序排序

使用heapq合并多个有序列表

merge_list = heapq.merge(list1, list2, list3, ..., listn), 结果仍然是一个有序列表。
时间复杂度:先建了个k大小的堆,就是O(k),然后最多对n-k个数据进行了heapreplace。所以总的时间复杂度是O(k+(n-k)logk)

array_a = [10, 7, 15, 8]
array_b = [17, 3, 8, 20, 13]
array_merge = heapq.merge(sorted(array_a), sorted(array_b))

> merge result: [3, 7, 8, 8, 10, 13, 15, 17, 20]

替换数据的方法

用于对容器有大小限制的场景,比如前K个高频元素

temp = heappushpop(heap, num),先将num添加到堆中,然后将堆顶的数据出堆【先入堆 再出堆】。
temp = heapreplace(heap, num),先将堆顶的数据出堆,然后将num添加到堆中 【先出堆 再入堆】。
时间复杂度:log(k)

array_c = [10, 7, 15, 8]
heapq.heapify(array_c)
# 先push再pop
item = heapq.heappushpop(array_c, 5)
> before: [7, 8, 15, 10]
> after:  [7, 8, 15, 10]
> item: 5

 
array_d = [10, 7, 15, 8]
heapq.heapify(array_d)
# 先pop再push
item = heapq.heapreplace(array_d, 5)
> before: [7, 8, 15, 10]
> after:  [5, 8, 15, 10]
> 7

参考:
https://blog.csdn.net/weixin_43790276/article/details/107741332
https://www.jianshu.com/p/38c15269c746
http://www.manongjc.com/detail/59-bndhkutdssmlqgi.html

猜你喜欢

转载自blog.csdn.net/LoveJSH/article/details/128552886