多重双向链表实现LFU算法

本文介绍了如何使用多重双向链表实现LFU算法,详细讲解了思路、核心定义以及源码实现。通过创建多个链表分别存储不同访问频率的数据,并在数据访问时动态调整其在链表中的位置,从而达到O(1)的时间复杂度。
摘要由CSDN通过智能技术生成

前言

昨天在 leetcode 460 上学习LFU算法,看见一个大佬写的O(1) 解法 —— 存储频次的HashMap改为直接用双向链表(最优实现 13ms 双100%),印象颇深,隔了一天之后照着他的思路自己也手写实现了LFU,和原版略有不同,不过思路很值得讲

源码

点击这里获取源码,别忘了点个Star哦~

一、思路

一般来说,我们理解的链表都是这样的:
双链表

我们可以通过双链表实现LRU,但是LFU呢?它比LRU要多出一个“访问频次”的属性,只靠双链表似乎并不能满足LFU的设计需求

那么我们用很多条链表来做这件事怎么样?既可以通过链表的特性保证访问时间和顺序的关系,又可以让每个链表记录一个频次,里面存放的都是对应频次的数据:
多重链表
在每条链表中又按照频次大小顺序连接着,这也可以形容为“链链表”,用代码来讲,就是:LinkedList<LinkedList<Node>>

那么要如何使用这多重链表呢

  • 在已有多条链表分别存储数据的情况下,put操作时,在频次最大的链表上进行头插(缓存满时,要删除频次最小的链表的尾节点)
  • 在缓存为空时,put操作时,让缓存生成一条频次为0的链表,并在该链表上进行头插
  • 在put,get操作命中缓存时,让缓存中被命中的数据节点移动到(当前频次+1)的频次链表上
  • 若频次最小的链表仅剩的节点被删除,则删除频次最小的链表(即对“链链表”进行尾删

二、核心定义

1. 类的层次

在我的容器设计中,我的类定义的层次如下

// 最外层,LFU缓存容器类
public class MultiLinkedListLFU<K, V> {
   
	// 中间层,多重链表类
	private class MultiLinkedList {
   
		// 最内层,链表节点类
		class Entry {
   
		
		}
	}
}

2. 链表节点类

我的链表节点类存放键值对,如下

        class Entry {
   
            K key;
            V val;

            Entry pre, next;

            Entry(K key, V val) {
   
                this.key = key;
                this.val = val;
            }
        }

没什么特别的,不熟的同学建议复习下双向链表

3. 多重链表类

1)类的字段

        /**
         * 记录被访问的频次
         */
        int freq;

        /**
         * 前/后链表
         */
        MultiLinkedList pre, next;

        /**
         * 当前链表的头/尾节点
         */
        Entry head, tail;

        /**
         * 当前链表的长度
         */
        private int size;

这个类的实例是一条链表,每条链表都记录着频次的字段freq,并且有前后指针MultiLinkedList pre, next指向其自身的上一条/下一条链表,除此之外,头/尾节点和链表长度的字段基本上都是一条双向链表要记录的字段

2)构造方法

        /**
         * 无参构造方法
         */
        MultiLinkedList() {
   
        }

        /**
         * 有参构造方法
         *
         * @param freq
         */
        MultiLinkedList(int freq) {
   
            this.freq = freq;
        }

有参构造方法为实例记录传入的频次,无参则默认生成频次为0的实例

3)方法实现

a. 添加数据方法put
        /**
         * 添加数据方法
         *
         * @param key
         * @param val
         */
        void put(K key, V val) {
   
            // 链表为空/不为空,分情况讨论
            if (size == 0) {
   
                head = new Entry(key, val);
                tail = head;
                ++size;
            } else {
   
                addToHead(new Entry(key, val));
            }
        }

addToHead方法就是头插方法

b. 删除指定key的节点方法
        /**
         * 通过key删除指定节点
         *
         * @param key
         */
        boolean removeEntryByKey(K key) {
   
            Entry entry = findEntryByKey(key);
            if (entry == null) {
   
                //未找到指定节点,删除失败
                return false;
            } else {
   
                // 当前key得到的节点是尾节点时,直接尾删
                
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值