前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >LFU -- Javascript实现版本

LFU -- Javascript实现版本

作者头像
奋飛
发布2021-12-30 21:06:33
4790
发布2021-12-30 21:06:33
举报
文章被收录于专栏:Super 前端Super 前端

LFU(Least Frequently Used)

LFU 最近最不常用,是一种常见的淘汰(置换)算法,选择最近使用次数最少的予以淘汰。常用于内存管理。

对每个块进行引用计数,当缓存达到容量上限并有新的模块插入时,系统将搜索具有最低计数的块删除。

在这里插入图片描述
在这里插入图片描述

算法实现:双向链表 + 哈希表

  1. 节点:Node {key, value, freq, pre, post}
    • key、value
    • freq:使用频率, 删除时使用
    • pre、post: 前置及后置节点,插入使用
  2. 双向链表:DualLinkedList {head, tail, remove(), add(), isEmpty()} 按照被使用的顺序存储 node,头部head为最近使用的,尾部tail是最久未使用的。
    • head、tail:头及尾节点
    • remove():移除节点
    • add():添加节点(追加到 head 节点之后)
    • isEmpty():链表是否为空
  3. 定义 Hash 表:
    • freqMap :以频率(freq)为索引,每个索引对应存放一个双向链表,链表中存放所有改频率的节点(key,value,freq
    • cacheMap: 以 key 为索引,每个索引对应存放相应的 node 节点
代码语言:javascript
复制
/* 定义节点 */
class Node {
    constructor (key, value) {
        this.key = key
        this.value = value
        this.freq = 1   	// 访问频次
        this.pre = null
        this.post = null
    }
}

/* 双向链 表*/
class DualLinkedList {
    constructor (node) {
        this.head = new Node()
        this.tail = new Node()
        this.head.post = this.tail
        this.tail.pre = this.head
    }

    /* 移除 */
    remove (node) {
        node.pre.post = node.post
        node.post.pre = node.pre
    }

    /* 插入节点(head之后) */
    add (node) {
        node.post = this.head.post
        this.head.post.pre = node
        node.pre = this.head
        this.head.post = node
    }

    /* 判空 */
    isEmpty () {
        return this.head.post === this.tail && this.tail.pre === this.head
    }
}

class LFUCache {
    constructor (capacity) {
        this.capacity = capacity
        this.minFreq = 0  				// 最小使用频率,删除使用
        this.size = 0     				// 当前已使用的容量
        this.freqMap = new Map()  // freq-DualLinkedList
        this.cacheMap = new Map() // key-node
    }

    // 频率+1,freqMap的旧频率的key中移除,添加到新的freq链表中
    increaseFreq (node) {
        let list = this.freqMap.get(node.freq)
        list.remove(node)
      	// 同步更新最新频率标记(如果当前是最小频率节点)
        if (list.isEmpty() && node.freq === this.minFreq) {
            this.minFreq += 1
        }
        node.freq += 1
        let newList = this.freqMap.get(node.freq)
        if (newList === undefined) {
            newList = new DualLinkedList()
        }
        newList.add(node)	// 最新频率索引对应的双向链表中增加该节点
        this.freqMap.set(node.freq, newList)
    }
 
  	/* 获取节点 value */
    get(key) {
        let node = this.cacheMap.get(key)
        if (node === undefined) {
          	// 不存在,返回 -1
            return -1
        }
      	// 存在,返回当前节点值;同时访问频次 +1
        this.increaseFreq(node)
        return node.value
    }

   	/* 增加节点 */ 	
    put (key, value) {
        if (this.capacity === 0) return
        // 如果key已存在,则变更其值,增加访问频率
        if (this.cacheMap.has(key)) {
            let node = this.cacheMap.get(key)
            node.value = value
            this.cacheMap.set(key, node)
            this.increaseFreq(node)
            return
        }
      	// 容量达到上限
        if (this.size === this.capacity) {
          	// 获取最小频率队列(通过minFreq标记)
            let miniFreqList = this.freqMap.get(this.minFreq)
            // 获取最小频次队列的队尾(最小且最久)
            let miniFreqNode = miniFreqList.tail.pre
            // 淘汰该节点
            miniFreqList.remove(miniFreqNode)
            this.freqMap.set(this.minFreq, miniFreqList)
            this.cacheMap.delete(miniFreqNode.key)
        }

      	// 插入新节点
        let node = new Node(key, value)
        this.cacheMap.set(key, node)
    		// freq为1的链表中,插入节点
        let list = this.freqMap.get(1)
        if (list === undefined) {
            list = new DualLinkedList()
        }
        list.add(node)

        this.freqMap.set(1, list)
        this.minFreq = 1
        if (this.size < this.capacity) {
            this.size += 1
        }
        return
    }
}

复杂度:

  • 时间复杂度:对于 putget 都是 O(1)
  • 空间复杂度:空间复杂度:O(capacity),因为哈希表和双向链表最多存储 $ capacity+1 $ 个元素。

借助 map 的 LRU 特性

map.delete(key)map.set(key, value) 则 map 对应的key被移到了尾部。

1、Let M be the this value. … 7、Append p as the last element of entries. 8、Return M. – Map.prototype.set

代码语言:javascript
复制
/**
 * 定义节点
 */
class Node {
    constructor (key, value) {
        this.key = key
        this.value = value
        this.freq = 1   // 访问频次
    }
}

class LFUCache {
    constructor (capacity) {
        this.capacity = capacity
        this.minFreq = 0  // 最小使用频率,删除使用
        this.size = 0     // 当前已使用的容量
        this.freqMap = new Map()  // 频率-map(不使用双向链表)
        this.cacheMap = new Map() // key-node(用于get方法,快捷获取相对应key的value值)
    }

    /**
     * 频率+1
     * ① 从freqMap的旧频率的key中移除
     * ② 旧频率为空 && 当前频率是最小频率,需要将最小频率+1
     * ③ 添加到新频率(旧频率+1)对应的map队列中 => 当前队列没有需要新建
     */
    increaseFreq (node) {
        let map = this.freqMap.get(node.freq)
        map.delete(node.key)
        if (!map.size && node.freq === this.minFreq) {
            this.minFreq += 1
        }
        node.freq += 1
        let newMap = this.freqMap.get(node.freq)
        if (newMap === undefined) {
            newMap = new Map()
        }
        newMap.set(node.key, node)
        this.freqMap.set(node.freq, newMap)
    }

  	// 借助缓存的 cacheMap 处理
    get(key) {
        let node = this.cacheMap.get(key)
        if (node === undefined) {
            return -1
        }
        this.increaseFreq(node)
        return node.value
    }

    put (key, value) {
        if (this.capacity === 0) return
        
        // 如果节点已存在,则变更其值
        if (this.cacheMap.has(key)) {
            let node = this.cacheMap.get(key)
            node.value = value
            this.cacheMap.set(key, node)
            this.increaseFreq(node)
            return
        }
      	// 达到最大容量,需要删除访问频次最小&&最久的节点
        // 访问频次最小=>通过minFreq标记获取相应的map
        // map中第一个元素为最久未被访问元素(map lru特性决定)
        if (this.size === this.capacity) {
            let miniFreqMap = this.freqMap.get(this.minFreq)

            let miniFreqNode = miniFreqMap.get(miniFreqMap.keys().next().value)    // 频次最小,第一个key(借助map的lru特性)
            miniFreqMap.delete(miniFreqNode.key)
            this.freqMap.set(this.minFreq, miniFreqMap)
            this.cacheMap.delete(miniFreqNode.key)
        }

      	// 增加节点元素(这里只处理节点不存在,已存在上面已处理)
        let node = new Node(key, value)
        this.cacheMap.set(key, node)
        let map = this.freqMap.get(1)
        if (map === undefined) {
            map = new Map()
        }
        map.set(node.key, node)
				// 对应新节点,访问频次一定是1,且最小频次为1
        this.freqMap.set(1, map)
        this.minFreq = 1
        if (this.size < this.capacity) {
            this.size += 1
        }
        return
    }
}

复杂度:

  • 时间复杂度:对于 putget 都是 O(1)
  • 空间复杂度:O(capacity)
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-10-24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • LFU(Least Frequently Used)
  • 借助 map 的 LRU 特性
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档