Python双链表详细讲解实现:模拟计算机缓存淘汰算法,FIFO,LRU以及LFU!

替换策略以及 cache的写操作策略

在这里插入图片描述

  • cache写入策略(类似redis 和 数据库的关系。 先写入缓存,还是先写入数据库)
    在这里插入图片描述

首先建立双向链表

  • 要注意的是,双链表增加头结点,会很方便操作
  • 还有双链表的插入删除操作
  • 上代码:
  • 功能
    • 弹出头部节点(pop)
    • 默认尾部添加结点(append),尾插法
    • 往头部添加结点(append_front),头插法
    • 删除节点(remove),不加参数时,删除尾部结点, 加蚕食时,删除指定结点
    • 打印 字符串格式化的 链表(print)
#! -*- encoding=utf-8 -*-

class Node:
    def __init__(self, key=0, value=0):
        self.key = key
        self.value = value
        self.prev = None
        self.next = None
    

    def __str__(self):
        val = '{%d: %d}' % (self.key, self.value)
        return val


class DoubleLinkedList():
    def __init__(self, capacity=0xffff):
        """
        param:  capacity: 链表容量
        param:  head: 头指针,指向头结点。 head.next == None。头结点的好处很多:
                1. 使首元结点(第一个正式存放数据的节点)无序进行特殊处理,表示都是 xxx.next
                2. 便于非空表和空表的统一处理。 判空为head.next == None。  不会存在头指针直接指向 None的情况
                3. 头结点还可存储一些信息。
        param:  tail: 尾指针,表为空时,头尾指针都指向头结点
        param:  cur_size: 当前链表长度
        """
        self.capacity = capacity
        self.head = Node()
        self.tail = self.head
        self.cur_size = 0


    def __add_head(self, node):
        """
            头插法向链表中插入数据,分三种情况:
            1. 为空链表
            2. 不为空, 没达到最大容量
            3. 达到最大容量
        """
        if not self.head.next:
            self.head.next = node
            node.prev = self.head
            self.tail = node
        elif self.cur_size < self.capacity:
            # 分析四个步骤,顺序还不能错。
            # node先将节点指向对应位置
            node.prev = self.head
            node.next = self.head.next
            # 在更新链表节点指针
            self.head.next.prev = node
            self.head.next = node
        else:
            pass

        self.cur_size += 1
        return node


    def __add_tail(self, node):
        """
            尾插法: 同样分三种情况
            设置头结点的好处就体现出来了! 本来还要进行分情况讨论链表是否为空,
            这里就不需要讨论了~ ,都进行统一处理。
        """
        self.tail.next = node
        node.prev = self.tail
        self.tail = self.tail.next
        self.cur_size +=1
		# 出 bug了! 这里的逻辑是当前插入的节点是新节点。 next = None。 但是如果是从一个链表中取出来该节点(__remove操作只是修改了 该节点前一个节点的next指针,后一个节点的prve指针。所以该节点next,prve指针,仍然没做改变,指向原来的前一个节点,后一个节点),放到另一个链表的尾部, 该节点的删除操作,netx指针还指向的有值,需要置None
        node.next = None
        return node
    

    def __remove(self, node=None):
        """
            删除任意节点:
            1. 当不传入node时,默认尾部删除
            2. node就是 尾部结点
            3. 删除其他位置结点
        """
        if not node or node == self.tail:
            self.__del_tail()    
        else:
            node.prev.next = node.next
            node.next.prev = node.prev
        
        self.cur_size -= 1
        return node


    def __del_head(self):
        """ 
            删除首元结点
            头结点使得处理逻辑都是一样的,直接调用 __remoove()方法 
        """
        return self.__remove(self.head.next)
    

    def __del_tail(self):
        """ 删除尾部结点 """    
        if self.head.next == None: # 空链表无法删除
            return 
        p = self.tail
        self.tail.prev.next = None
        self.tail = self.tail.prev
        self.cur_size -= 1
        e = self.tail
        return p


    # 提供对外的API接口
    def pop(self):
        """ 弹出头部节点 """
        return self.__del_head()


    def append(self, node):
        """ 默认尾部添加结点 """
        return self.__add_tail(node)


    def append_front(self, node):
        """ 往头部添加结点 """
        return self.__add_head(node)


    def remove(self, node = None):
        """ 删除节点 """
        return self.__remove(node)


    def print(self):
        """ 打印当前链表 """
        p = self.head
        line = ''
        while p.next:
            line += '%s' % p.next
            line += ' ==> ' 
            p = p.next

        print(line)



# 测试
if __name__ == '__main__':
    l = DoubleLinkedList(10)
    nodes = []
    for i in range(10):
        node = Node(i, i)
        nodes.append(node)

    l.append(nodes[0])
    l.print()
    l.append(nodes[1])
    l.print()
    l.pop()
    l.print()
    l.append(nodes[2])
    l.print()
    l.append_front(nodes[3])
    l.print()
    l.append(nodes[4])
    l.print()
    l.remove(nodes[2])
    l.print()
    l.remove()
    l.print()

测试结果的正确性:运行结果如下
[root@localhost 计算机组成]# python3 双向链表实现.py 
{0: 0} ==> 
{0: 0} ==> {1: 1} ==> 
{1: 1} ==> 
{1: 1} ==> {2: 2} ==> 
{3: 3} ==> {1: 1} ==> {2: 2} ==> 
{3: 3} ==> {1: 1} ==> {2: 2} ==> {4: 4} ==> 
{3: 3} ==> {1: 1} ==> {4: 4} ==> 
{3: 3} ==> {1: 1} ==> 

缓存置换算法实现

  • 下列代码实现只是考虑了缓存替换并没有考虑主存的相关操作

(一)FIFO(先进先出算法)

在这里插入图片描述

from DoubleLinkedList import DoubleLinkedList, Node


class FIFOCache(object):

    def __init__(self, capacity):
        """
        param:  capacity: 也是用来设置容量大小的
        param:  map: 定义映射表。加快链表元素的查找速度。
                     key就是 链表key,value就是 node节点本身的引用
        param:  list: 双向链表对象
        param:  list: 当前元素个数
        """
        self.capacity = capacity
        self.map = {}
        self.list = DoubleLinkedList(self.capacity)
        self.size = 0

    
    def get(self, key):
        """ 实现get方法 
            找到返回value, 找不到返回 -1
        """
        node = self.map.get(key, None)
        if not node: return -1

        return node.value


    def put(self, key, value):
        """ 实现put方法
            if 如果已存在:
                则1.更新该节点对应的value,2.将该节点从原位置删除,3.将节点添加到尾部
                (好处是,少了重复创建结点)
            else:不存在就判断是否插入新的
                if 容量已满:
                    需要删除(头部节点, 删除map中的映射)
                添加
        """
        node = self.map.get(key, None)
        if node: 
            # node存在,相当于更新操作,需要删除原来位置,在添加到链表尾
            self.list.remove(node)
            node.value = value
            self.list.append(node)
        else: 
            if self.size == self.capacity:
                node = self.list.pop()
                del self.map[node.key]
                self.size -= 1
            node = Node(key, value)
            self.list.append(node)
            self.map[key] = node
            self.size += 1
        

    def print(self):
        self.list.print()



# 测试
if __name__ == '__main__':
    cache = FIFOCache(2)
    cache.put(1, 1)
    cache.print()
    cache.put(2, 2)
    cache.print()
    print(cache.get(1))
    cache.put(3, 3)
    cache.print()
    print(cache.get(2))
    cache.print()
    cache.put(4, 4)
    cache.print()
    print(cache.get(1))

# 运行结果
[root@localhost 计算机组成]# python3 FIFO.py 
{1: 1} ==> 
{1: 1} ==> {2: 2} ==> 
1
{2: 2} ==> {3: 3} ==> 
2
{2: 2} ==> {3: 3} ==> 
{3: 3} ==> {4: 4} ==> 
-1

(二)LRU(最近最少使用算法)

在这里插入图片描述

  • 上代码实现:

from DoubleLinkedList import DoubleLinkedList, Node


class LRUCache():

    def __init__(self, capacity):
        self.capacity = capacity
        self.map = {}
        self.list = DoubleLinkedList(self.capacity)
        self.size = 0

    def get(self, key):
        """
            LRU算法,当在链表中时,不仅缓存,还要将缓存置于链表头部
        """
        node = self.map.get(key, None)

        if not node: return -1
        self.list.remove(node)
        self.list.append_front(node)
        return node.value


    def put(self, key, value):
        """
            if 已经存在该结点:
                (实际上就是更新cache了)
                1. 删除原来在的位置
                2. 更新值value
                3. 重新插入到链表头部
            else: 
                if 缓存已经满了(达到长度)
                    删除最后一个元素(最近最少使用的结点)
                    表头加入新元素
                else:
                    未满,直接表头加元素
        """
        node = self.map.get(key, None)

        if node:
            self.list.remove(node)
            node.value = value
            self.list.append_front(node)
        else:
            node = Node(key, value)
            if self.size == self.capacity:
                old_node = self.list.remove()
                self.map.pop(old_node.key)
                self.size -= 1

            self.list.append_front(node)
            self.map[key] = node
            self.size += 1

    
    def print(self):
        self.list.print()


# 测试
if __name__ == '__main__':
    cache = LRUCache(2)
    cache.put(2, 2)
    cache.print()
    cache.put(1, 1)
    cache.print()
    cache.put(3, 3)
    cache.print()
    print(cache.get(1))
    cache.print()
    print(cache.get(2))
    cache.print()
    print(cache.get(3))
    cache.print()


# 运行结果
[root@localhost 计算机组成]# python3 LRU.py 
{2: 2} ==> 
{1: 1} ==> {2: 2} ==> 
{3: 3} ==> {1: 1} ==> 
1
{1: 1} ==> {3: 3} ==> 
-1
{1: 1} ==> {3: 3} ==> 
3
{3: 3} ==> {1: 1} ==> 

(三)LFU(最不经常使用算法)

在这里插入图片描述

  • 上代码实现
from DoubleLinkedList import DoubleLinkedList, Node


class LFUNode(Node):
    """ 继承Node节点类,新加属性freq表示频率 """

    def __init__(self, key, value) -> None:
        super(LFUNode, self).__init__(key, value)
        # 频率
        self.freq = 0


class LFUCache():

    def __init__(self, capacity):
        """
        param:  map: 依然保存所有节点的映射,方便查询 
        param:  freq_map: 字典类型。
                    key: 频率, value:频率对应的双链表。
                    当发生淘汰时:
                    1. 选择频率最低的链表
                    2. 选择从链表头部删除(因为尾部添加元素, 头部的元素在时间上,也是最久未被访问的)

        param:  size: 当前节点个数。
                    没有一个链表存储所有节点。 而是把节点都分开,按照频率,存放在每个小的链表里
        param:  capacity: 总容量 
        """
        self.capacity = capacity
        self.map = {}
        self.freq_map = {}
        self.size = 0


    def __update_freq(self, node):
        """ 更新结点频率 """
        freq = node.freq
        # 将原来所以在的频率链表的节点删除
        node = self.freq_map[freq].remove(node)
        if self.freq_map[freq].cur_size == 0:
            del self.freq_map[freq]
        # 更新后插入到新的频率链表
        freq += 1
        node.freq = freq
        if freq not in self.freq_map: 
            self.freq_map[freq] = DoubleLinkedList()
        # 向链表尾部添加
        self.freq_map[freq].append(node)  




    def get(self, key):
        node = self.map.get(key, None)
        if not node: return -1
        # 找到节点,更新节点的频率
        self.__update_freq(node)

        return node.value

    
    def put(self, key, value):
        node = self.map.get(key, None)
        # 缓存命中的时候
        if node: 
            node.value = value
            self.__update_freq(node)
        # 缓存不命中
        else:
            # 容量已满,需要淘汰
            if self.size >= self.capacity:
                min_freq = min(self.freq_map)
                # 从头部去掉
                node = self.freq_map[min_freq].pop()
                del self.map[node.key]
                self.size -= 1
            node = LFUNode(key, value)
            node.freq = 1
            self.map[key] = node
            # 如果不存在该频率(1频率) 的链表,那就创建一个先
            if node.freq not in self.freq_map:
                self.freq_map[node.freq] = DoubleLinkedList()
            # 尾部添加
            node = self.freq_map[node.freq].append(node)  
            self.size += 1
    
    def print(self):
        print('-'*50)
        for k, v in self.freq_map.items():
            print('freq = %d' % k)
            v.print()
        print('$'*50)


# 测试代码
if __name__ == "__main__":
    cache = LFUCache(2)
    cache.put(1, 1)
    cache.print()
    cache.put(2, 2)
    cache.print()
    print(cache.get(1))
    cache.print()
    cache.put(3, 3)
    cache.print()
    print(cache.get(2))
    cache.print()
    print(cache.get(3))
    cache.print()
    cache.put(4, 4)
    cache.print()
    print(cache.get(1))
    cache.print()
    print(cache.get(3))
    cache.print()
    print(cache.get(4))
    cache.print()
    

# 测试结果
[root@localhost 计算机组成]# python3 LFU.py 
--------------------------------------------------
freq = 1
{1: 1} ==> 
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
--------------------------------------------------
freq = 1
{1: 1} ==> {2: 2} ==> 
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
1
--------------------------------------------------
freq = 1
{2: 2} ==> 
freq = 2
{1: 1} ==> 
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
--------------------------------------------------
freq = 1
{3: 3} ==> 
freq = 2
{1: 1} ==> 
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
-1
--------------------------------------------------
freq = 1
{3: 3} ==> 
freq = 2
{1: 1} ==> 
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
3
--------------------------------------------------
freq = 2
{1: 1} ==> {3: 3} ==> 
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
--------------------------------------------------
freq = 2
{3: 3} ==> 
freq = 1
{4: 4} ==> 
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
-1
--------------------------------------------------
freq = 2
{3: 3} ==> 
freq = 1
{4: 4} ==> 
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
3
--------------------------------------------------
freq = 1
{4: 4} ==> 
freq = 3
{3: 3} ==> 
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
4
--------------------------------------------------
freq = 3
{3: 3} ==> 
freq = 2
{4: 4} ==> 
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值