Python内置数据结构算法
常用内置数据结构和算法
数据结构/算法 | 语言内置 | 内置库 |
---|---|---|
线性结构 | list(列表)/tuple(元祖) | array(数组,不常用)/collections.namedtuple |
链式结构 | collections.deque(双端队列) | |
字典结构 | dict(字典) | collections.Counter(计数器)/OrderedDict(有序字典) |
集合结构 | set(集合)/frozenset(不可变集合) | |
排序算法 | sorted | |
二分算法 | bisect模块 | |
堆算法 | heapq模块 | |
缓存算法 | functools.lru_cache(Least Recent Used, python3) |
import collections
print('*******namedtuple********')
Point = collections.namedtuple('Point', 'x, y')
p = Point(1, 2)
print(p.x)
print(p.y)
print(p[0])
print(p[1])
print('*******deque********')
de = collections.deque()
de.append(1)
de.appendleft(0)
print(de)
print(de.pop())
print(de.popleft())
print('*******Counter********')
c = collections.Counter("abcdab")
print(c)
print(c['a'])
print(c.most_common())
print('*******OrderedDict********')
od = collections.OrderedDict()
od['c'] = 'c'
od['a'] = 'a'
od['b'] = 'b'
# orderedDict的key顺序是第一次插入的顺序
print(list(od.keys()))
print('*******defaultdict********')
dd = collections.defaultdict(int)
print(dd['a'])
dd['b'] += 1
print(dd)
Python dict底层结构
dict底层使用的哈希表
为了支持快速查找使用了哈希表作为底层结构
哈希表平均查找时间复杂度O(1)
CPython解释器使用二次探查解决哈希冲突问题
哈希表如何解决哈希冲突?
'''
(一)线性探测法
线性探测法是最简单的处理冲突的方法。
(1)插入元素:插入元素时,如果发生冲突,算法将从该槽位向后遍历哈希表,直到找到表中的下一个空槽,并将该值放入到空槽当中。
(2)查找元素:查找元素时,首先散列值所指向的槽,如果没有找到匹配,则继续从该槽向后遍历哈希表,直到:1)找到相应的元素;2)找到一个空槽(指示查找的元素不存在);3)整个哈希表都遍历完毕(指示该元素不存在并且哈希表已满)
线性探测法存在的缺点:
(1)处理溢出需要另编程序。一般可以设立一个溢出表,用来存放上述哈希表中放不下的记录。此溢出表最简单的结构是顺序表,查找方法可用顺序查找;
(2)删除工作很复杂。因为一旦对某一个元素删除后,该位置出现空槽,后续查找到该空槽时会认为该元素不存在。需要一种方法对删除元素进行标记;
(3)由于每次都是线性递增,容易导致堆聚,即存入哈希表的记录在表中都连成一片,后续发生冲突的可能性会越大。
(二)二次探测法 cpython使用的是这种方式
本质和线性探测法较类似,只不过线性探测法按照1,2,3,4……的步长进行探测,二次探测法按照1,4,9,16……即平方的方式进行探测。
(三)链地址法
也成为拉链法。其基本思路为:将所有具有相同哈希地址的而不同关键字的元素连接到同一个单链表中。如果选定的哈希表长度为m,则可以将哈希表定义为一个有m个头指针组成的指针数组。凡是给定哈希地址为i的元素,均以节点的形式插入到下标为i的头指针单链表中。并且最新的元素插入到链表的前端,这不仅因为方便,还因为一个经常发生的事实是:最新插入的元素最有可能不久又被访问。
对于冲突的哈希值,将其链入到该地址所对应的链表头中。
链地址法的特点:
(1)冲突处理简单,并且没有堆积现象;
(2)由于链地址法各个链表上的结点空间时动态申请的,所以比较适合于造表前没有确定表长的情况;
(3)删除结点的操作比较容易实现,直接对链表中的结点删除即可。
'''
哈希表如何进行扩容?
'''
哈希表的扩容
1. 为什么要扩容
1. 扩容即是将哈希表的长度增加,通常是变为原来的两倍
2. 使用链地址法封装哈希表时, 填装因子(loaderFactor)会大于1,
理论上这种封装的哈希表时可以无限插入数据的
3. 但是但是随着数据量的增多,哈希表中的每个元素会变得越来越长, 这是效率会大大降低
4. 因此, 需要通过扩容来提高效率
2. 如何扩容
1. 扩容就是将哈希表的存储空间增加为原来的两倍
2. 此时,需要将所有数据项都进行修改(需要重新调用哈希函数,来获取新的位置)
3. 哈希表扩容是一个比较耗时的过程,但是功在当代, 利在千秋
3. 什么情况下扩容
常见的情况是在填装因子(loaderFactor) > 0.75是进行扩容
'''
Python list/tuple区别
都是线性结构,支持下标访问
list是可变对象,tuple保存的引用不可变
list没法作为字典的key , tuple 可以(可变对象不可hash )
什么是 LRUCache ?
Least-Recently-Used替换掉最近最少使用的对象
缓存剔除策略,当缓存空间不够用的时候需要一种方式剔除key
常见的有LRU,LFU等
LRU通过使用一个循环双端队列不断把最新访问的key 放到表头实现
如何实现LRUCache ?
字典用来缓存,循环双端链表用来记录访问顺序
利用Python内置的dict + collections.OrderedDict 实现
dict用来当做kv键值对的缓存
OrderedDict用来实现更新最近访问的 key
from collections import OrderedDict
class LRUCache(object):
def __init__(self, capacity=128):
self.od = OrderedDict()
self.capacity = capacity
def get(self, key): # 每次访问更新最新使用的key
if key in self.od:
val = self.od[key]
self.od.move_to_end(key) # 将key放到队尾
return val
else:
return -1
def put(self, key, value): # 更新k/v
if key in self.od:
del self.od[key]
self.od[key] = value # 更新key到表头
else:
self.od[key] = value
if len(self.od) > self.capacity:
self.od.popitem(last=False) # 删除最早的元素
算法
排序+查找,重中之重
排序算法︰冒泡排序、快速排序、归并排序、堆排序
线性查找,二分查找等
能独立实现代码(手写),能够分析时间空间复杂度
常用排序算法的时空复杂度
排序算法 | 最差时间分析 | 平均时间复杂度 | 稳定度 | 空间复杂度 |
---|---|---|---|---|
冒泡排序 | O(n^2) | O(n^2) | 稳定 | O(1) |
选择排序 | O(n^2) | O(n^2) | 不稳定 | O(1) |
插入排序 | O(n^2) | O(n^2) | 稳定 | O(1) |
快速排序 | O(n^2) | O(n*log2n) | 不稳定 | O(log2n)~O(n) |
堆排序 | O(n*log2n) | O(n*log2n) | 不稳定 | O(1) |
数据结构之链表
链表有单链表、双链表、循环双端链表
#[https://leetcode-cn.com/problems/reverse-linked-list]: 反转一个单链表
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
pre = None
cur = head
while cur:
nextnode = cur.next
cur.next = pre
pre = cur
cur = nextnode
return pre
数据结构之队列
队列(queue)是先进先出结构
如何使用 Python 实现队列?
实现队列的apend和pop操作,如何做到先进先出
使用Python的 list或者collections.deque 实现队列
from collections import deque
class Queue:
def __init__(self):
self.items = deque()
def append(self, val):
return self.items.append(val)
def pop(self):
return self.items.popleft()
def empty(self):
return len(self.items) == 0
def test_queue():
q = Queue()
q.append(0)
q.append(1)
q.append(2)
print(q.pop())
print(q.pop())
print(q.pop())
if __name__ == '__main__':
test_queue()
数据结构之栈
栈(stack)是后进先出结构
如何使用Python 实现栈?
实现栈的push和pop操作,如何做到后进先出
同样可以用Python list 或者collections.deque 实现栈
from collections import deque
class Queue:
def __init__(self):
self.items = deque() # 或者使用list
def append(self, val):
return self.items.append(val)
def pop(self):
return self.items.pop()
def empty(self):
return len(self.items) == 0
def test_queue():
q = Queue()
q.append(0)
q.append(1)
q.append(2)
print(q.pop())
print(q.pop())
print(q.pop())
if __name__ == '__main__':
test_queue()
数据结构之字典与集合
Python dict/set底层都是哈希表
哈希表的实现原理,底层其实就是一个数组
根据哈希函数快速定位一个元素,平均查找O(1),非常快
不断加入元素会引起哈希表重新开辟空间,拷贝之前元素到新数组
数据结构之二叉树
先序、中序、后序遍历
先(根)序∶先处理根,之后是左子树,然后是右子树
中(根)序∶先处理左子树,然后是跟,然后是右子树
后(根)序 : 先处理左子树,然后是右子树,最后是根
class BinTreeNode(object):
def __init__(self, data, left=None, right=None):
self.data, self.left, self.right = data, left, right
class BinTree(object):
def __init__(self, root):
self.root = root
def preorder_trav(self, subtree):
'''先(根)序'''
if subtree is not None:
print(subtree.data)
self.preorder_trav(subtree.left)
self.preorder_trav(subtree.right)
def inorder_trav(self, subtree):
'''中(根)序'''
if subtree is not None:
self.inorder_trav(subtree.left)
print(subtree.data)
self.inorder_trav(subtree.right)
def postorder_trav(self, subtree):
'''后(根)序'''
if subtree is not None:
self.postorder_trav(subtree.left)
self.postorder_trav(subtree.right)
print(subtree.data)
数据结构之堆
堆其实是完全二叉树,有最大堆和最小堆
最大堆∶对于每个非叶子节点V,V的值都比它的两个孩子大
最大堆支持每次pop操作获取最大的元素,最新堆获取最小元素
常见问题∶用堆来完成 topk问题,从海量数字中寻找最大的k个
import heapq
class TopK:
'''
获取大量元素topk大个元素,固定内存
思路:
1.先放入元素前k个建立一个最小堆
2.迭代剩余元素:
如果当前元素小于堆顶元素,跳过该元素(肯定不是前K大)
否则替换堆顶元素为当前元素,并重新调整堆
'''
def __init__(self, iterable, k):
self.minheap = []
self.capacity = k
self.iterable = iterable
def push(self, val):
if len(self.minheap) >= self.capacity:
min_val = self.minheap[0]
if val < min_val:
pass
else:
heapq.heapreplace(self.minheap, val) # 返回并且pop堆顶最小值,推入新的val值并调整堆
else:
heapq.heappush(self.minheap, val)
def get_topk(self):
for val in self.iterable:
self.push(val)
return self.minheap
def test():
import random
i = list(range(1000))
random.shuffle(i)
print(i)
_ = TopK(i, 10)
print(_.get_topk())
if __name__ == '__main__':
test()