list在索引、加入/删除队尾元素时是O(1)的,但是在删除元素时是O(n)的;
deque在list基础上,删除队头元素是O(1)的,但是删除中间元素是O(n)的;
dict在增、删、改、查都是O(1)的,但是由于内存地址不连续,无法通过下标索引,所以无法实现删除第一个和最后一个元素的功能;
链表增/删任意元素都是O(1)的,但是查询时是O(n)的。
因为本文结合双向链表
和字典
实现增、删、改、查都是O(1)的数据结构。
首先定义一个双向链表,作为字典【键值对】的value
,并记录之前和之后的元素,方便从两个方向增删。
class ListNode:
def __init__(self,key=None,val=None):
self.key = key # 键
self.val = val # 值
self.pre = None
self.next = None
其次定义一个字典,通过键来查询对应的链表结点。
定义虚拟头结点和尾结点,实现在链表左侧和右侧插入/删除元素的功能,且时间复杂度为O(1);删除任意元素时,首先通过字典查到在链表中的位置,进而再在链表中删除该结点,时间复杂度都为O(1)。
需要注意的是,每次增加和删除元素既要在链表中操作,也要在字典中操作。
class ListMap:
def __init__(self):
self.hash = dict()
self.head = ListNode()
self.tail = ListNode()
self.head.next = self.tail
self.tail.pre = self.head
def pushleft(self, key, val): # 在链表左侧插入元素结点
node = ListNode(key,val)
self.hash.setdefault(key,node) # 在字典中加入该键值对
tmp = self.head.next
self.head.next = node # 建立新结点和原先链表的关系
node.next = tmp
tmp.pre = node
node.pre = self.head
def pushright(self, key, val): # 在链表右侧插入元素结点
node = ListNode(key,val)
self.hash.setdefault(key,node)
tmp = self.tail.pre
self.tail.pre = node
node.pre = tmp
tmp.next = node
node.next = self.tail
def get(self, key): # 通过键来查询值
if key in self.hash:
return self.hash[key]
else:
return None
def remove(self, key): # 删除任意存在的元素
if self.get(key):
node = self.hash[key]
node.pre.next = node.next
node.next.pre = node.pre
del self.hash[key]
def popleft(self): # 删除链表左侧元素,实现deque.popleft()的功能
node = self.head.next
self.head.next = node.next
node.next.pre = self.head
del self.hash[node.key]
return node.val
def popright(self): # 删除链表右侧元素,实现list.pop()的功能
node = self.tail.pre
self.tail.pre = node.pre
node.pre.next = self.tail
del self.hash[node.key]
return node.val
def size(self): # 返回字典的长度
return len(self.hash)
使用时需要先将ListMap实例化,之后就可以直接调用自定义类的函数实现增删改查了。
利用上述结构就实现了LRU 缓存