LRU算法 C语言简单实现

背景

  LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。

示例

在这里插入图片描述

  可以使用双向链表来将缓冲区页面串联起来,如上图所示,页面4,7,0被相继读入缓冲区,第四步页面需要访问页面7,然后就会去遍历该链表,发现找到了7号页面,而且此时页面没有全部用满,那么将页面7提到表头(通过更改双向链表的指针指向实现),然后页面1被读到缓冲区,然后读取页面0,遍历链表找到了页面0并将页面0提到表头,其他步骤类似,再看看置换,比如最后一步,读取页面6,遍历链表后发现页面6没有找到,此时缓冲区已满,这时需要从缓冲区中置换掉链表最尾部(最近最少使用)的页面4进行淘汰,并且把页面6放在表头。PostgreSQL数据库中的SLRU算法思想也是基于此。

代码实现
struct HashNode {
    struct LRUNode *Node;
    struct HashNode *Next;
};

struct LRUNode {
    int Key;
    int Value;
    struct LRUNode *Next;
    struct LRUNode *Pre;
};

typedef struct {
    int Capacity;
    int Size;
    struct HashNode **Table;
    struct LRUNode *Head;
    struct LRUNode *Tail;
} LRUCache;

int Hash(int key, int capacity) {
    return key % capacity;
}

void Insert(struct HashNode *T, struct LRUNode *P) {
    //向哈希表某一排插,如果是正常哈希表,一般在这里面算哈希值
    //但对于这里因为插之前一定是算过哈希的,所以可以直接往那一排来插
    struct HashNode *NewNode = malloc(sizeof(struct HashNode));
    NewNode->Node = P;
    NewNode->Next = T->Next;
    T->Next = NewNode;
}

void Remove(LRUCache *obj, struct LRUNode *Target) {//满了,在双向链表和哈希中删去tail
    //在哈希表中删
    int index = Hash(Target->Key, obj->Capacity);
    struct HashNode *P = obj->Table[index]->Next;
    struct HashNode *Pre = obj->Table[index];
    while(P!=NULL) {
        if(P->Node->Key==Target->Key) {
            Pre->Next = P->Next;
            free(P);
            break;
        }
        P = P->Next;
        Pre = Pre->Next;
    }
    //在双向链表中删
    obj->Tail->Pre = Target->Pre;
    Target->Pre->Next = obj->Tail;
    //不释放Target,因为接下来可以继续用,只需修改下key,value
}

LRUCache* lRUCacheCreate(int capacity) {
    LRUCache *obj = malloc(sizeof(LRUCache));
    obj->Capacity = capacity;
    obj->Size = 0;
    //创建哈希表
    obj->Table = malloc(sizeof(struct HashNode *) * capacity);
    for(int i=0; i<capacity; i++) {
        obj->Table[i] = malloc(sizeof(struct HashNode));
        obj->Table[i]->Node = malloc(sizeof(struct LRUNode));
        obj->Table[i]->Next = NULL;
    }
    //创建双向链表
    obj->Head = malloc(sizeof(struct LRUNode));
    obj->Tail = malloc(sizeof(struct LRUNode));
    obj->Head->Pre = NULL;
    obj->Head->Next = obj->Tail;
    obj->Tail->Pre = obj->Head;
    obj->Tail->Next = NULL;
    obj->Head->Key = -1;
    obj->Tail->Key = -1;
    obj->Head->Value = 0;
    obj->Tail->Value = 0;
    return obj;
}

void Update(struct LRUNode *H, struct LRUNode *Target) {
    if(!(Target->Pre==NULL&&Target->Next==NULL)) {//不是新加入的点,要调整一下之前的结构
        Target->Next->Pre = Target->Pre;
        Target->Pre->Next = Target->Next;
    }
    Target->Next = H->Next;
    Target->Next->Pre = Target;
    H->Next = Target;
    Target->Pre = H;
}

int lRUCacheGet(LRUCache* obj, int key) {
    int index = Hash(key, obj->Capacity);
    struct HashNode *P = obj->Table[index]->Next;
    while (P!=NULL)
    {
        if(P->Node->Key == key) {
            Update(obj->Head, P->Node);
            return P->Node->Value;
        }
        P = P->Next;
    }
    return -1;
}

void lRUCachePut(LRUCache* obj, int key, int value) {
    int index = Hash(key, obj->Capacity);
    int flag = 1;//记录哈希表中是否已存在,1不存在,0存在
    struct HashNode *P = obj->Table[index]->Next;
    while(P!=NULL) {
        if(P->Node->Key==key) {
            P->Node->Value = value;
            flag = 0;
            Update(obj->Head, P->Node);
            break;
        }
        P = P->Next;
    }
    if(flag) {//不存在,新建hashnode放进去
        struct HashNode *NewHashNode = malloc(sizeof(struct HashNode));
        if(obj->Size==obj->Capacity) {//满了,需要删
            struct LRUNode *Target = obj->Tail->Pre;
            Remove(obj, Target);
            (obj->Size)--;
            Target->Key = key;
            Target->Value = value;
            Target->Pre = NULL;
            Target->Next = NULL;
            NewHashNode->Node = Target;//直接拿来用,不用新分配了
        } else {
            NewHashNode->Node = malloc(sizeof(struct LRUNode));
            NewHashNode->Node->Key = key;
            NewHashNode->Node->Value = value;
            NewHashNode->Node->Next = NULL;
            NewHashNode->Node->Pre = NULL;
        }
        //在双向链表插
        Update(obj->Head, NewHashNode->Node);
        //在哈希表插
        int addIndex = Hash(key, obj->Capacity);
        Insert(obj->Table[addIndex], NewHashNode->Node);
        (obj->Size)++;
    }
    
}

void lRUCacheFree(LRUCache* obj) {
    free(obj->Table);
    free(obj->Head);
    free(obj->Tail);
    free(obj);
}

C++案例
在这里插入图片描述

struct DLinkedNode {
    int key, value;
    DLinkedNode* prev;
    DLinkedNode* next;
    /* 构造函数便于新定义节点时初始化对象 */
    DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}
    DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};

class LRUCache {
private:
    unordered_map<int, DLinkedNode*> map;
    DLinkedNode* head;
    DLinkedNode* tail;
    int size;       // 缓冲区使用的大小
    int capacity;   // 缓冲区的容量

public:
    LRUCache(int _capacity): capacity(_capacity), size(0) {
        /* 使用伪头部和伪尾部节点 */
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head->next = tail;
        tail->prev = head;
    }
    /* 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 */
    int get(int key) {
        if (!map.count(key)) return -1;
        /* 如果 key 存在,先通过哈希表定位,再移到头部 */
        DLinkedNode* node = map[key];
        moveToHead(node);
        return node->value;
    }
    
    void put(int key, int value) {
        if (!map.count(key)) {
            /* 如果 key 不存在,创建一个新的节点 */
            DLinkedNode* node = new DLinkedNode(key, value);
            /* 添加进哈希表 */
            map[key] = node;
            /* 添加至双向链表的头部 */
            addToHead(node);
            /* 缓存大小+1 */
            size++;
            if (size > capacity) {
                DLinkedNode* removed = removeTail();// 如果超出容量,删除双向链表的尾部节点
                map.erase(removed->key);// 删除哈希表中对应的项
                delete removed;// 防止内存泄漏
                size--;// 缓存大小-1
            }
        }
        else {
            /* 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部 */
            DLinkedNode* node = map[key];
            node->value = value;
            moveToHead(node);
        }
    }

/****************定义双向链表需要用的API函数****************/
public:
    /* 在虚拟头节点后添加新的节点 node */
    void addToHead(DLinkedNode* node) {
        node->prev = head;
        node->next = head->next;
        head->next->prev = node;
        head->next = node;
    }
    /* 删除当前节点 node 这里也是为什么使用双向链表的原因方便删除节点 */
    void removeNode(DLinkedNode* node) {
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }
    /* 把出现频率比较高的节点放入 头部 */
    void moveToHead(DLinkedNode* node) {
        removeNode(node);
        addToHead(node);
    }
    /* 移除尾部节点 并返回尾部最后一个节点 */
    DLinkedNode* removeTail() {
        DLinkedNode* node = tail->prev;
        removeNode(node);
        return node;
    }
};
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
LRU(Least Recently Used)算法是一种缓存淘汰算法,它的基本思想是根据数据的使用频率来淘汰最近最少使用的数据。以下是用C语言实现LRU算法的示例代码: ```c #include <stdio.h> #include <stdlib.h> // 定义LRU结点的结构体 typedef struct Node { int key; // 结点的键 int value; // 结点的值 struct Node* next; // 指向下一个结点的指针 struct Node* prev; // 指向前一个结点的指针 } Node; // 定义LRU缓存的结构体 typedef struct LRUCache { int capacity; // 缓存的容量 int size; // 缓存的当前大小 Node* head; // 指向链表头节点的指针 Node* tail; // 指向链表尾节点的指针 Node** hashmap; // 哈希表,保存结点的指针 } LRUCache; // 初始化LRU缓存 LRUCache* lRUCacheCreate(int capacity) { LRUCache* cache = (LRUCache*)malloc(sizeof(LRUCache)); cache->capacity = capacity; cache->size = 0; cache->head = NULL; cache->tail = NULL; cache->hashmap = (Node**)calloc(capacity, sizeof(Node*)); return cache; } // 获取LRU缓存中指定key对应的value int lRUCacheGet(LRUCache* obj, int key) { if (obj->hashmap[key] == NULL) { return -1; } // 将访问的结点移到链表头部 Node* node = obj->hashmap[key]; if (node != obj->head) { if (node == obj->tail) { obj->tail = obj->tail->prev; } else { node->next->prev = node->prev; node->prev->next = node->next; } node->next = obj->head; node->prev = NULL; obj->head->prev = node; obj->head = node; } return node->value; } // 向LRU缓存中插入新的key-value对 void lRUCachePut(LRUCache* obj, int key, int value) { if (obj->hashmap[key] == NULL) { // 如果缓存已满,删除最近最少使用的结点 if (obj->size == obj->capacity) { Node* tail = obj->tail; obj->hashmap[tail->key] = NULL; obj->tail = tail->prev; if (obj->tail != NULL) { obj->tail->next = NULL; } else { obj->head = NULL; } free(tail); obj->size--; } // 创建新的结点并加入链表头部 Node* node = (Node*)malloc(sizeof(Node)); node->key = key; node->value = value; node->next = obj->head; node->prev = NULL; if (obj->head != NULL) { obj->head->prev = node; } else { obj->tail = node; } obj->head = node; obj->hashmap[key] = node; obj->size++; } else { // 更新结点的值并移到链表头部 Node* node = obj->hashmap[key]; node->value = value; if (node != obj->head) { if (node == obj->tail) { obj->tail = obj->tail->prev; } else { node->next->prev = node->prev; node->prev->next = node->next; } node->next = obj->head; node->prev = NULL; obj->head->prev = node; obj->head = node; } } } // 释放LRU缓存的内存 void lRUCacheFree(LRUCache* obj) { Node* curr = obj->head; while (curr != NULL) { Node* tmp = curr->next; free(curr); curr = tmp; } free(obj->hashmap); free(obj); } int main() { LRUCache* cache = lRUCacheCreate(2); lRUCachePut(cache, 1, 1); lRUCachePut(cache, 2, 2); printf("%d\n", lRUCacheGet(cache, 1)); // 输出1 lRUCachePut(cache, 3, 3); printf("%d\n", lRUCacheGet(cache, 2)); // 输出-1 lRUCachePut(cache, 4, 4); printf("%d\n", lRUCacheGet(cache, 1)); // 输出-1 printf("%d\n", lRUCacheGet(cache, 3)); // 输出3 printf("%d\n", lRUCacheGet(cache, 4)); // 输出4 lRUCacheFree(cache); return 0; } ``` 以上代码是用C语言实现LRU算法简单示例,包括创建缓存、获取键对应的值、插入新的键值对以及释放内存的功能。代码中使用了双向链表和哈希表来实现LRU缓存,保证了缓存的快速查找和结点的插入/删除操作。在main函数中演示了LRU缓存的使用过程,并输出了一些操作的结果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值