C语言实现LRU缓存机制——哈希表+双向链表

题目描述

题目来源:
https://leetcode-cn.com/problems/lru-cache/
运用你所掌握的数据结构,设计和实现一个LRU(最近最少使用)缓存机制。它应该支持以下操作:获取数据get和写入数据 put。
获取数据get(key)-如果关键字(key)存在于缓存中,则获取关键字的值(总是正数),否则返回-1。
写入数据put(key,value)-如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组[关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
要求:时间复杂度为O(1):

思路:1

1、一开始想的是用一个类似于数组栈来实现,栈中的的数据结构为一个key值和value值:
在这里插入图片描述
类似于这样,在put的时候,如果栈中元素已满例如存放的数据如下:
在这里插入图片描述

下一步要存放(4,4),数据元素已满,那么依次下移元素:
在这里插入图片描述
在插入元素:(4,4)
在这里插入图片描述

这样就能保证插入的元素在最顶部,如果元素已满,把最久未使用的元素删除。
在Get的时候,如果元素在内部,则把该元素置顶,其他元素依次下移:代码如下:

typedef struct{    
	int key;   
 	int value;
 }Stack;
 typedef struct {    
 	Stack *S;
 	int top;    
 	int Max_size;
 } LRUCache;

LRUCache* lRUCacheCreate(int capacity) 
{    
	LRUCache *obj = (LRUCache *)malloc(sizeof(LRUCache));    
	obj->S = (Stack *)malloc(sizeof(Stack)*capacity);    
	obj->top = -1;    
	obj->Max_size=capacity;    
	return obj;
}
int lRUCacheGet(LRUCache* obj, int key) {
	
	int i = 0;    
	Stack temp;    
	int j = 0;    
	for(i;i<=obj->top;i++)    
	{        
		if(obj->S[i].key == key)        
		{            
			temp = obj->S[i];            
			for( j = i;j<obj->top;j++)            
			{                
				obj->S[j]=obj->S[j+1];            
			}            
			obj->S[j] = temp;            
			return temp.value;        
		}            
	}    
	return -1;
}
void lRUCachePut(LRUCache* obj, int key, int value) {
    	int i =0;    
    	int j = 0;    
    	Stack temp;    
    	for(i;i<=obj->top;i++)    
    	{        
    		if(obj->S[i].key == key)        
    		{            
    			obj->S[i].value = value;            
    			temp = obj->S[i];            
    			for(j=i;j<obj->top;j++)            
    			{                
    				obj->S[j]=obj->S[j+1];            
    			}            
    			obj->S[j] = temp;            
    			return ;        
    			}    
    		}    
    		if(i<obj->Max_size)    
    		{        
	    		obj->S[i].key = key;        
	    		obj->S[i].value = value;        
	    		obj->top++;        
	    		return ;    
	    	}else{        
	    		int temp_1 = i-1;               
	    		i=0;        
	    		for(i;i<obj->top;i++)        
	    		{            
	    			obj->S[i] = obj->S[i+1];        
	    		}        
	    		obj->S[temp_1].key = key;        
	    		obj->S[temp_1].value = value;        
	    		return ;   
	    	}
}
void lRUCacheFree(LRUCache* obj) {    free(obj);
}

这一种思路的时间复杂度很大;
在这里插入图片描述

思路:二

题目要求时间复杂度为O(1);
(1)数组的查找时间复杂度为1,但是增加数据和删除数据时时间复杂度很高
(2)链表无法做到更新,查找为1.
(3)哈希表数据是没有顺序的,没有办法找到最久未使用。
可以把链表和哈希表两中数据结构混合使用,哈希表中保存链表中数据的映射,可以快速找到链表中的数据,进而实现时间复杂度近似为1。
例如:要查找2这个数据,在哈希表中可以快速查找到2这个数,之后在链表中实现删除和增加元素。
在这里插入图片描述

具体思路:
(1):对于get操作,首先判断key是否存在:如果key不存在,则返回一1
如果key存在,则key对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置,并将其移动到双向链表的头部,最后返回该节点的值。
(2)对于put 操作,首先判断key是否存在:如果key 不存在,创建一个新的节点,在双向链表的头部添加该节点,并将key和该节点添加进哈希表中。然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项;。如果key存在,则与get操作类似,先通过哈希表定位,再将对应的节点的值更新为value,并将该节点移到双向链表的头部。

代码实现

typedef struct node{    
	int val;    
	int key;    
	struct node *pre;   
	struct node *next;
}Node,*LinkList;//双向链表节点结构
typedef struct {    
	LinkList store;//用来存放数据    
	LinkList *next;//使用拉链发处理冲突
}Hash;//哈希表数据结构
typedef struct {        
	int size;//当前缓存大小    
	int capacity;//缓存容量    
	Hash* table;//哈希表    
	LinkList head;// 指向最近使用的数据    
	LinkList tail;// 指向最久未使用的数据    
} LRUCache;
Hash * HashMap(Hash *table,int key,int capacity){    
	int addr = key % capacity;    
	return &table[addr];
}
void HeadInsertion(LinkList head, LinkList cur)
{//双链表头插法    
	if (cur->pre == NULL && cur->next == NULL)    
	{// cur 不在链表中                
		cur->pre = head;        
		cur->next = head->next;        
		head->next->pre = cur;        
		head->next = cur;    
	} else 
	{// cur 在链表中        
		LinkList first = head->next;//链表的第一个数据结点
		if ( first != cur)        
		{//cur 是否已在第一个            
		cur->pre->next = cur->next;//改变前驱结点指向        
		cur->next->pre = cur->pre;//改变后继结点指向         
		cur->next = first;//插入到第一个结点位置           
		cur->pre = head;            
		head->next = cur;            
		first->pre = cur;        
		}    
	}
}
LRUCache* lRUCacheCreate(int capacity) {
    LRUCache* obj = (LRUCache*)malloc(sizeof(LRUCache));
    obj->table = (Hash*)malloc(capacity * sizeof(Hash));    
    memset(obj->table, 0, capacity * sizeof(Hash));    
    obj->head = (LinkList)malloc(sizeof(Node));    
    obj->tail = (LinkList)malloc(sizeof(Node));    //创建头、尾结点并初始化   
     obj->head->pre = NULL;    
     obj->head->next = obj->tail;    
     obj->tail->pre = obj->head;    
     obj->tail->next = NULL;    
     //初始化缓存 大小 和 容量     
     obj->size = 0;    
     obj->capacity = capacity;    
     return obj;
     }
int lRUCacheGet(LRUCache* obj, int key) {    
	Hash* addr = HashMap(obj->table, key, obj->capacity);//取得哈希地址    
	addr = addr->next;//跳过头结点    
	if (addr == NULL){       
	 return -1;    
	 }   
	 while ( addr->next != NULL && addr->store->key != key)    
	 {//寻找密钥是否存在        
	 	addr = addr->next;    
	 }   if (addr->store->key == key)   
	  {//查找成功        
	  	HeadInsertion(obj->head, addr->store);//更新至表头        
	  	return addr->store->val;    
	  }    
	  	return -1;
}
void lRUCachePut(LRUCache* obj, int key, int value) {
    Hash* addr = HashMap(obj->table, key, obj->capacity);//取得哈希地址    
    if (lRUCacheGet(obj, key) == -1)    
    {        
    	if (obj->size>= obj->capacity)
	{                        
		LinkList last = obj->tail->pre->pre;            	
		LinkList del = last->next;            
		last->next = obj->tail;            
		obj->tail->pre = last;                        
		Hash *delt = HashMap(obj->table,del->key,obj->capacity);//找到要删除的地址            
		Hash *help_delt = delt;            
		delt = delt->next;            
		while(delt->store->key != del->key)            
		{               
			 help_delt = delt;//删除的前一个节点                
			 delt = delt->next;            
		}            
		help_delt->next = delt->next;           
		 delt->store = NULL;            
		 delt->next=NULL;            
		 free(delt);

            Hash * new_insert = (Hash*)malloc(sizeof(Hash));
            new_insert->next = addr->next;            
            addr->next = new_insert;
            new_insert->store = del;            
            del->key = key;           
             del->val=value;            
             HeadInsertion(obj->head,del);                  
       }        
       else        
       {//LPU未满            
       Hash* new_node = (Hash *)malloc(sizeof(Hash));            		
       new_node->store = (LinkList)malloc(sizeof(Node));            
       new_node->next = addr->next;            
       addr->next = new_node;            
       new_node->store->pre  = NULL;            
       new_node->store->next = NULL;            
       new_node->store->val  = value;            
       new_node->store->key  = key;            
       HeadInsertion(obj->head,new_node->store);            
       (obj->size)++;                   
        }                  
   }    
   else   
    {        
    obj->head->next->val = value;//替换数据值    
    }
}
void lRUCacheFree(LRUCache* obj) {    
	free(obj->table);    
	free(obj->head);    
	free(obj->tail);    
	free(obj);
}

在这里插入图片描述

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个利用哈希表双向链表实现LRU页面置换算法的C语言代码示例: ```c #include <stdio.h> #include <stdlib.h> #define CACHE_CAPACITY 5 // 缓存容量为5 // 定义缓存节点 typedef struct CacheNode { int key; // 缓存键 int value; // 缓存值 struct CacheNode *prev; // 上一个节点指针 struct CacheNode *next; // 下一个节点指针 } CacheNode; // 定义哈希表节点 typedef struct HashNode { int key; // 哈希键 CacheNode *value; // 哈希值,为指向缓存节点的指针 struct HashNode *next; // 下一个节点指针 } HashNode; // 初始化缓存节点 CacheNode *initCacheNode(int key, int value) { CacheNode *node = (CacheNode *)malloc(sizeof(CacheNode)); node->key = key; node->value = value; node->prev = NULL; node->next = NULL; return node; } // 初始化哈希表 HashNode **initHashTable() { HashNode **hashTable = (HashNode **)malloc(CACHE_CAPACITY * sizeof(HashNode *)); for (int i = 0; i < CACHE_CAPACITY; i++) { hashTable[i] = (HashNode *)malloc(sizeof(HashNode)); hashTable[i]->key = -1; // 哈希键设为-1,表示无效节点 hashTable[i]->value = NULL; hashTable[i]->next = NULL; } return hashTable; } // 计算哈希值 int hash(int key) { return key % CACHE_CAPACITY; } // 向哈希表中添加节点 void addHashNode(HashNode **hashTable, int key, CacheNode *node) { int index = hash(key); HashNode *hashNode = hashTable[index]; while (hashNode->next != NULL) { // 找到哈希表尾部 hashNode = hashNode->next; } HashNode *newNode = (HashNode *)malloc(sizeof(HashNode)); // 创建新节点 newNode->key = key; newNode->value = node; newNode->next = NULL; hashNode->next = newNode; // 将新节点插入哈希表尾部 } // 从哈希表中删除节点 void removeHashNode(HashNode **hashTable, int key) { int index = hash(key); HashNode *hashNode = hashTable[index]; while (hashNode->next != NULL) { if (hashNode->next->key == key) { // 找到待删除节点 HashNode *temp = hashNode->next; hashNode->next = temp->next; // 将待删除节点从哈希表中移除 free(temp); break; } hashNode = hashNode->next; } } // 查找哈希表中是否存在指定键 CacheNode *findHashNode(HashNode **hashTable, int key) { int index = hash(key); HashNode *hashNode = hashTable[index]; while (hashNode->next != NULL) { if (hashNode->next->key == key) { // 找到指定键的哈希节点 return hashNode->next->value; } hashNode = hashNode->next; } return NULL; } // 将缓存节点移动到链表头部 void moveToHead(CacheNode *node) { CacheNode *prev = node->prev; CacheNode *next = node->next; prev->next = next; next->prev = prev; node->prev = NULL; node->next = NULL; prev = next = node; } // 利用LRU算法获取缓存中指定键的值 int getCacheValue(HashNode **hashTable, CacheNode **head, CacheNode **tail, int key, int value) { CacheNode *node = findHashNode(hashTable, key); if (node != NULL) { // 如果键已经在缓存中,将其移动到链表头部 moveToHead(node); return node->value; } else { // 如果不在缓存中,将其添加到链表头部 node = initCacheNode(key, value); addHashNode(hashTable, key, node); node->next = *head; (*head)->prev = node; *head = node; if (*tail == NULL) { // 如果链表为空,将尾指针指向头指针 *tail = *head; } if (hashTable[key % CACHE_CAPACITY]->next->next != NULL) { // 如果缓存已经满了,将链表尾部的节点移除 HashNode *lastNode = hashTable[(*tail)->key % CACHE_CAPACITY]; removeHashNode(hashTable, (*tail)->key); *tail = (*tail)->prev; (*tail)->next = NULL; free(lastNode->next); } return -1; } } int main() { HashNode **hashTable = initHashTable(); CacheNode *head = NULL; CacheNode *tail = NULL; int cache[][2] = {{1, 10}, {2, 20}, {3, 30}, {4, 40}, {5, 50}, {6, 60}, {7, 70}, {8, 80}, {9, 90}, {1, 100}, {2, 200}, {3, 300}, {4, 400}}; // 测试数据 for (int i = 0; i < sizeof(cache) / sizeof(cache[0]); i++) { int value = getCacheValue(hashTable, &head, &tail, cache[i][0], cache[i][1]); if (value != -1) { printf("hit: %d\n", value); } else { printf("miss: %d\n", cache[i][1]); } } return 0; } ``` 以上就是一个基于哈希表双向链表实现LRU页面置换算法的C语言代码示例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值