分别用链表和数组实现LRU

基本概念

链表的概念:链表通过指针将一组零散的内存块串联在一起,属于线性结构。

链表的种类:单链表、循环链表、双向链表

链表与数据的区别:
1、复杂度方面。
2、读取方面。数组使用的是连续的内存空间,可以借助 CPU 的缓存机制,访问效率更高。而链表在内存中并不是连续存储,对 CPU 缓存不友好。
3、存储方面。数组大小固定,占用连续内存空间,扩容麻烦;链表本身没有大小限制,支持动态扩容。

链表实现LRU

思路

维护一个有序双链表,越靠近链表尾部的结点是越早之前访问的。当有一个新的数据被访问时,如下处理操作:
1、如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据对应的结点,并将其从原来的位置删除,然后再插入到链表的头部。
2、 如果此数据没有在缓存链表中,又可以分为两种情况:
如果此时缓存未满,则将此结点直接插入到链表的头部;
如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表的头部。
可以引入散列表,来记录每个数据位置,将缓存访问的时间复杂度降到O(1)

代码实现

#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;

struct DListNode {
    int key;
    int val;
    DListNode* pre;
    DListNode* next;
    DListNode(int k, int v) :key(k), val(v), pre(nullptr), next(nullptr) {};
};

class LruCache {
private:
    int size  = 0;
    DListNode* head;
    DListNode* tail;
    unordered_map<int, DListNode*> mp; //存放key及对应的node
public:
    LruCache(int size) {
       this->size = size;
       head = NULL;
       tail = NULL;
       this->head = new DListNode(0, 0);
       this->tail = new DListNode(0, 0);
       this->head->next = this->tail;
       this->tail->pre = this->head;
    }
    vector<int> LRU(vector<vector<int>>& operators, int k);
    /*
    LRU Cache具备get操作:
    如果key在缓存中,则把对应的节点放到链表头部,并返回对应的value值
    如果key不在缓存中,则返回-1·
    */
    int get(int key);
    /*
    LRU Cache具备set操作:
    如果key在缓存中,则先重置对应的value,然后将对应的结点移动到链表头部
    如果key不在缓存中,则新建一个结点并放在链表头部,如果cache满,则最后一个结点删除。
    */
    void set(int key, int val);
    void insertFirst(DListNode* node);
    void removeLast();
    void moveToHead(DListNode* node);
};

vector<int> LruCache::LRU(vector<vector<int>>& operators, int k) {
    if (k < 1) return {};
    this->size = k;
    this->head = new DListNode(0, 0);
    this->tail = new DListNode(0, 0);
    this->head->next = this->tail;
    this->tail->pre = this->head;

    if (operators.size() == 0) return {};

    vector<int> res;

    for(vector<int> op: operators) {
        if(op[0] == 1) {
	        set(op[1], op[2]);
	    } else {
	        if (op[0] == 2) {
	           int value = get(op[1]);
	           res.push_back(value);
	        }
	    }
    }
    return res;
}

void LruCache::set(int key, int value) {
    if (mp.find(key) == mp.end()) {
        DListNode* node = new DListNode(key, value);
	    mp[key] = node;
	    if (this->size <= 0) {
	       removeLast();
	    } else {
	       this->size--;
	    }
	    insertFirst(node);
    } else {
        mp[key]->val = value;
	    moveToHead(mp[key]);
    }
}

void LruCache::removeLast() {
   mp.erase(this->tail->pre->key);
   this->tail->pre->pre->next = this->tail;
   this->tail->pre = this->tail->pre->pre;
}

void LruCache::insertFirst(DListNode* node) {
    node->next = this->head->next;
    node->pre = this->head;
    this->head->next->pre = node;
    this->head->next = node;
}

void LruCache::moveToHead(DListNode* node) {
    if (node->pre == this->head) return;
    node->next->pre = node->pre;
    node->pre->next = node->next;
    insertFirst(node);
}

int LruCache::get(int key) {
    int ret = -1;
    if (mp.find(key) != mp.end()){
        ret = mp[key]->val;
	    moveToHead(mp[key]);
    }
    return ret;
}
int main() {
    LruCache* lru = new LruCache(4) ;
    lru->set(1, 1);
    lru->set(2, 2);
    lru->set(3, 3);
    lru->set(4, 4);
    lru->set(5, 5);
    cout << lru->get(2) << endl;
    lru->set(5, 5);
    lru->set(6, 6);
    lru->set(3, 4);
    lru->set(4, 4);
    lru->set(7, 7);
    cout << lru->get(2) << endl;
}

使用数组实现LRU

思路

维护一个数组,两个下标,一个头部下标,一个尾部下标,越靠近尾部下标的是越早之前访问的。通过对下标取模,把该数组变相当成循环队列使用。当有一个新的数据被访问时,如下处理操作:
1、如果此数据之前已经被缓存在数组中了,我们通过散列表获取这个数据对应的数组下标位置,并将其从原来的位置删除,然后再插入到数组的头部,头部位置-1。被删除位置后的数据逐一往前移动一个下标,更新尾部。
2、 如果此数据没有在缓存链表中,又可以分为两种情况:
如果此时缓存未满,则将此结点直接插入到数组的头部;
如果此时缓存已满,则数组尾结点删除,将新的数据结点插入数组的头部。
可以引入散列表,来记录每个数据在数组中的下标位置,将缓存访问的时间复杂度降到O(1)

代码

#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;

class LruCache {
private:
    int size;
    int capacity;
    int * array;
    int head;
    int tail;
    unordered_map<int, int> mp; //存放key及其在array中pos
public:
    LruCache(int size) {
       this->size = size;
       capacity = size + 1;
       array = new int[size + 1];
       head = 0;
       tail = 0;
    }
    ~LruCache() {
        if(array != nullptr) {
	    delete[] array;
	}
    }
    vector<int> LRU(vector< vector<int> > & operators, int k);
    int get(int key);
    void set(int key);
    void insertFirst(int key);
    void removeLast();
    void moveToHead(int key, int index);
    void Print();
};

vector<int> LruCache::LRU(vector< vector<int> > & operators, int k) {
    vector<int> res;
    if (k < 1) return res;
    this->size = k;
    delete[] this->array;
    this->array = new int[k];
    head = 0;
    tail = 0;

    if (operators.size() == 0) return res;

    for(vector<int> op: operators) {
        if(op[0] == 1) {
	    set(op[1]);
	} else {
	    if (op[0] == 2) {
	       int value = get(op[1]);
	       res.push_back(value);
	    }
	}
    }
    return res;
}

void LruCache::set(int key) {
    if (mp.find(key) == mp.end()) {
	if (this->size <= 0) {
	   removeLast();
	}
	insertFirst(key);
    } else {
	moveToHead(key, mp[key]);
    }
}

void LruCache::Print() {
    int pos = head;
    while(pos != tail) {
        cout << array[pos] << " ";
	pos = (pos + 1) % capacity;
    }
    cout <<  endl;
    cout << "head:" << head <<" tail:" << tail << " lrusize:" << size << " mpsize" << mp.size() << endl;
}

void LruCache::removeLast() {
   int pre_index = (tail - 1 + capacity) % capacity;
   auto iter = mp.find(array[pre_index]);
   if(iter != mp.end()) {
       mp.erase(iter);
   }
   tail = pre_index;
   size++;
}

void LruCache::insertFirst(int key) {
    head = (head - 1 + capacity) % capacity;
    array[head] = key;
    mp[key] = head;
    size--;
}

void LruCache::moveToHead(int key, int index) {
    if(mp[key] == head) {
        return;
	//就在头部不需要move
    }
    //先插入
    head = (head - 1 + capacity) % capacity;
    array[head] = key;
    mp[key] = head;
    //再移动
    while(tail != index + 1) {
        int next_index = (index + 1) % capacity;
        array[index] = array[next_index];
	mp[array[next_index]] = index;
	index = next_index;
    }
    tail = (tail - 1 + capacity) % capacity;
}

int LruCache::get(int key) {
    int ret = -1;

    if (mp.find(key) != mp.end()){
        int index = mp[key];
	moveToHead(key, index);
	return index;
    }

    return ret;
}

int main() {
    LruCache* lru = new LruCache(4) ;
    lru->set(1);
    lru->Print();
    lru->set(2);
    lru->Print();
    lru->set(3);
    lru->Print();
    lru->set(4);
    lru->Print();
    lru->set(5);
    lru->Print();
    lru->set(5);
    lru->Print();
    lru->set(6);
    lru->Print();
    lru->set(3);
    lru->Print();
    lru->set(4);
    lru->Print();
    lru->set(7);
}

输出结果:

1
head:4 tail:0 lrusize:3 mpsize1
2 1
head:3 tail:0 lrusize:2 mpsize2
3 2 1
head:2 tail:0 lrusize:1 mpsize3
4 3 2 1
head:1 tail:0 lrusize:0 mpsize4
5 4 3 2
head:0 tail:4 lrusize:0 mpsize4
5 4 3 2
head:0 tail:4 lrusize:0 mpsize4
6 5 4 3
head:4 tail:3 lrusize:0 mpsize4
3 6 5 4
head:3 tail:2 lrusize:0 mpsize4
4 3 6 5
head:2 tail:1 lrusize:0 mpsize4
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值