基本概念
链表的概念:链表通过指针将一组零散的内存块串联在一起,属于线性结构。
链表的种类:单链表、循环链表、双向链表
链表与数据的区别:
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