从0开始回顾数据结构---LRU,LFU算法

简介: 题意解释请为LFU缓存设计一个数据结构。支持两种操作:get和set。● get(key) : 如果key在缓存中,则返回key对应的值;否则返回-1;● put(key, value): 如果key在缓存中,则更新key对应的值;否则插入(key, value),如果缓存已满,则先删除使用频率最小的key,如果有多个key具有相同的使用频率,则应该删除最久未使用的key。C++代码class LFUCache {public: struct Node { Node *left, *right; int key, val;

LRU,LFU算法

1、LRU 缓存

题意解释

请为LRU缓存设计一个数据结构。支持两种操作:getset

  • get(key) :  如果key在缓存中,则返回key对应的值(保证是正的);否则返回-1
  • put(key, value):  如果key在缓存中,则更新key对应的值;否则插入(key, value),如果缓存已满,则先删除上次使用时间最老的key

思路

(双链表+哈希)  

使用一个双链表和一个哈希表:

  • 双链表存储一个节点被使用(get或者put)的时间戳,且按最近使用时间从左到右排好序,最先被使用的节点放在双链表的第一位,因此双链表的最后一位就是最久未被使用的节点;

  • 哈希表存储key对应的链表中的节点地址,用于key-value 的增删改查;  

初始化:

  • n 是缓存大小;
  • 双链表和哈希表都为空;

get(key):

首先用哈希表判断key是否存在:

  • 如果key不存在,则返回-1;
  • 如果key存在,则返回对应的value,同时将key对应的节点放到双链表的最左侧;

put(key, value):

首先用哈希表判断key是否存在:

  • 如果key存在,则修改对应的value,同时将key对应的节点放到双链表的最左侧;
  • 如果key不存在:
  • 如果缓存已满,则删除双链表最右侧的节点(上次使用时间最老的节点),更新哈希表;
  • 否则,插入(key, value):同时将key对应的节点放到双链表的最左侧,更新哈希表;

时间复杂度分析:双链表和哈希表的增删改查操作的时间复杂度都是 O(1),所以getput操作的时间复杂度也都是O(1) 。

对应的双链表的几种操作

1、删除p节点

p->right->left = p->left;
 p->left->right = p->right;


2、在L节点之后插入p节点

p->right = L->right;
p->left = L;
L->right->left = p;
L->right = p;


时间复杂度分析:双链表和哈希表的增删改查操作的时间复杂度都是 ,所以get和set操作的时间复杂度也都是

C++代码

class LRUCache {
public:
    //定义双链表
    struct Node{
        int key,value;
        Node* left ,*right;
        Node(int _key,int _value): key(_key),value(_value),left(NULL),right(NULL){}
    }*L,*R;//双链表的最左和最右节点,不存贮值。
    int n;
    unordered_map<int,Node*>hash;
    void remove(Node* p)
    {
        p->right->left = p->left;
        p->left->right = p->right;
    }
    void insert(Node *p)
    {
        p->right = L->right;
        p->left = L;
        L->right->left = p;
        L->right = p;
    }
    LRUCache(int capacity) {
        n = capacity;
        L = new Node(-1,-1),R = new Node(-1,-1);
        L->right = R;
        R->left = L;    
    }
    
    int get(int key) {
        if(hash.count(key) == 0) return -1; //不存在关键字 key 
        auto p = hash[key];
        remove(p);
        insert(p);//将当前节点放在双链表的第一位
        return p->value;
    }
    
    void put(int key, int value) {
        if(hash.count(key)) //如果key存在,则修改对应的value
        {
            auto p = hash[key];
            p->value = value;
            remove(p);
            insert(p);
        }
        else 
        {
            if(hash.size() == n) //如果缓存已满,则删除双链表最右侧的节点
            {
                auto  p = R->left;
                remove(p);
                hash.erase(p->key); //更新哈希表
                delete p; //释放内存
            }
            //否则,插入(key, value)
            auto p = new Node(key,value);
            hash[key] = p;
            insert(p);
        }
    }
};

2、LFU 缓存

题意解释

请为LFU缓存设计一个数据结构。支持两种操作:getset

  • get(key) :  如果key在缓存中,则返回key对应的值;否则返回-1
  • put(key, value):  如果key在缓存中,则更新key对应的值;否则插入(key, value),如果缓存已满,则先删除使用频率最小的key,如果有多个key具有相同的使用频率,则应该删除最久未使用的key。

C++代码

class LFUCache {
public:
    struct Node {
        Node *left, *right;
        int key, val;
        Node(int _key, int _val) {
            key = _key, val = _val;
            left = right = NULL;
        }
    };
    struct Block {
        Block *left, *right;
        Node *head, *tail;
        int cnt;
        Block(int _cnt) {
            cnt = _cnt;
            left = right = NULL;
            head = new Node(-1, -1), tail = new Node(-1, -1);
            head->right = tail, tail->left = head;
        }
        ~Block() {
            delete head;
            delete tail;
        }
        void insert(Node* p) {
            p->right = head->right;
            head->right->left = p;
            p->left = head;
            head->right = p;
        }
        void remove(Node* p) {
            p->left->right = p->right;
            p->right->left = p->left;
        }
        bool empty() {
            return head->right == tail;
        }
    }*head, *tail;
    int n;
    unordered_map<int, Block*> hash_block;
    unordered_map<int, Node*> hash_node;
    void insert(Block* p) {  // 在p的右侧插入新块,cnt是p->cnt + 1
        auto cur = new Block(p->cnt + 1);
        cur->right = p->right;
        p->right->left = cur;
        p->right = cur;
        cur->left = p;
    }
    void remove(Block* p) {
        p->left->right = p->right;
        p->right->left = p->left;
        delete p;
    }
    LFUCache(int capacity) {
        n = capacity;
        head = new Block(0), tail = new Block(INT_MAX);
        head->right = tail, tail->left = head;
    }
    int get(int key) {
        if (hash_block.count(key) == 0) return -1;
        auto block = hash_block[key];
        auto node = hash_node[key];
        block->remove(node);
        if (block->right->cnt != block->cnt + 1) insert(block);
        block->right->insert(node);
        hash_block[key] = block->right;
        if (block->empty()) remove(block);
        return node->val;
    }
    void put(int key, int value) {
        if (!n) return;
        if (hash_block.count(key)) {
            hash_node[key]->val = value;
            get(key);
        } else {
            if (hash_block.size() == n) {
                auto p = head->right->tail->left;
                head->right->remove(p);
                if (head->right->empty()) remove(head->right);
                hash_block.erase(p->key);
                hash_node.erase(p->key);
                delete p;
            }
            auto p = new Node(key, value);
            if (head->right->cnt > 1) insert(head);
            head->right->insert(p);
            hash_block[key] = head->right;
            hash_node[key] = p;
        }
    }
};
/**
 * Your LFUCache object will be instantiated and called as such:
 * LFUCache* obj = new LFUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */


相关文章
|
21天前
|
存储 算法 Java
算法系列之数据结构-二叉树
树是一种重要的非线性数据结构,广泛应用于各种算法和应用中。本文介绍了树的基本概念、常见类型(如二叉树、满二叉树、完全二叉树、平衡二叉树、B树等)及其在Java中的实现。通过递归方法实现了二叉树的前序、中序、后序和层次遍历,并展示了具体的代码示例和运行结果。掌握树结构有助于提高编程能力,优化算法设计。
45 9
 算法系列之数据结构-二叉树
|
18天前
|
算法 Java
算法系列之数据结构-Huffman树
Huffman树(哈夫曼树)又称最优二叉树,是一种带权路径长度最短的二叉树,常用于信息传输、数据压缩等方面。它的构造基于字符出现的频率,通过将频率较低的字符组合在一起,最终形成一棵树。在Huffman树中,每个叶节点代表一个字符,而每个字符的编码则是从根节点到叶节点的路径所对应的二进制序列。
38 3
 算法系列之数据结构-Huffman树
|
20天前
|
算法 Java
算法系列之数据结构-二叉搜索树
二叉查找树(Binary Search Tree,简称BST)是一种常用的数据结构,它能够高效地进行查找、插入和删除操作。二叉查找树的特点是,对于树中的每个节点,其左子树中的所有节点都小于该节点,而右子树中的所有节点都大于该节点。
62 22
|
1月前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
96 29
|
1月前
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
121 25
|
1月前
|
存储 人工智能 算法
C 408—《数据结构》算法题基础篇—数组(通俗易懂)
408考研——《数据结构》算法题基础篇之数组。(408算法题的入门)
80 23
|
3月前
|
存储 运维 监控
探索局域网电脑监控软件:Python算法与数据结构的巧妙结合
在数字化时代,局域网电脑监控软件成为企业管理和IT运维的重要工具,确保数据安全和网络稳定。本文探讨其背后的关键技术——Python中的算法与数据结构,如字典用于高效存储设备信息,以及数据收集、异常检测和聚合算法提升监控效率。通过Python代码示例,展示了如何实现基本监控功能,帮助读者理解其工作原理并激发技术兴趣。
93 20
|
2月前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
68 2
|
16天前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于生物地理算法的MLP多层感知机优化matlab仿真
本程序基于生物地理算法(BBO)优化MLP多层感知机,通过MATLAB2022A实现随机数据点的趋势预测,并输出优化收敛曲线。BBO模拟物种在地理空间上的迁移、竞争与适应过程,以优化MLP的权重和偏置参数,提升预测性能。完整程序无水印,适用于机器学习和数据预测任务。
|
6天前
|
算法 数据安全/隐私保护 异构计算
基于LSB最低有效位的音频水印嵌入提取算法FPGA实现,包含testbench和MATLAB对比
本项目展示了一种基于FPGA的音频水印算法,采用LSB(最低有效位)技术实现版权保护与数据追踪功能。使用Vivado2019.2和Matlab2022a开发,完整代码含中文注释及操作视频。算法通过修改音频采样点的最低有效位嵌入水印,人耳难以察觉变化。然而,面对滤波或压缩等攻击时,水印提取可能受影响。该项目运行效果无水印干扰,适合实时应用场景,核心逻辑简单高效,时间复杂度低。