<think>嗯,我现在需要设计一个LRU缓存的数据结构,用C语言实现。用户要求get和put操作的时间复杂度都是O(1)。那我得先回忆一下LRU的原理。LRU是最近最少使用,当缓存满了的时候,淘汰最久未使用的那个数据。
首先,怎么实现O(1)的get和put呢?通常,哈希表可以提供O(1)的查找,但哈希表本身无法维护元素的顺序。而链表可以方便地维护访问顺序,但查找是O(n)的。所以可能需要结合这两种数据结构。比如,使用哈希表来快速定位节点,而用双向链表来维护使用顺序。每次访问一个节点时,把它移到链表头部,这样链表尾部就是最久未使用的,需要淘汰的时候删除尾部节点。
那具体来说,数据结构应该包含一个哈希表和一个双向链表。哈希表的键是缓存的key,值是对应的链表节点。每个节点包含key、value,以及前驱和后继指针。这样,当get一个key时,通过哈希表找到节点,然后将该节点移动到链表头部,这样时间复杂度是O(1)。put的时候,如果key存在,更新值并移动到头部;如果不存在,创建新节点插入头部,同时如果超过容量,就删除尾部节点,并在哈希表中删除对应的key。
但C语言里没有内置的哈希表,需要自己实现。或者,可以用一个固定大小的数组,或者用开放寻址法来处理冲突。但为了简单起见,可能应该使用链表法来处理哈希冲突。或者,考虑到题目中的capacity是初始化时给定的正整数,可以设计哈希表的桶的数量为capacity,或者更大一些,以减少冲突。或者,更简单的方式,可以使用一个结构体数组来作为哈希表,每个节点包含key和对应的链表节点指针。不过这样的话,查找可能需要遍历链表,这样在哈希冲突的情况下,时间复杂度可能退化为O(n),但题目要求平均O(1),所以需要好的哈希函数和合适的哈希表大小,尽量减少冲突。
或者,是否可以使用更高效的结构?比如,C语言中可以用uthash这样的库,但用户可能需要自己实现。所以可能需要自己实现一个哈希表,或者用其他方式。
或者,或许可以采用更简单的方式,比如每个链表节点同时作为哈希表的一个元素,使用双向链表和哈希表的结合。比如,每个节点包含key、value、prev、next,然后用哈希表来保存key到节点的映射。这样,当需要查找key的时候,直接通过哈希表找到对应的节点,然后调整链表位置。
那现在问题是如何在C语言中实现这个哈希表。假设capacity是给定的,那么可以预先分配一个足够大的哈希表数组,或者用链表来处理冲突。但为了时间效率,可能需要一个高效的哈希表实现。比如,使用开放寻址法,线性探测或者双重哈希。或者,考虑到每个节点的key是整数,可以使用一个直接寻址的方式,但这样的话如果key的范围很大,比如超过capacity的话,会浪费内存,所以可能不太行。因此,可能需要使用哈希函数将key映射到哈希表的索引,比如取模运算。例如,哈希表的size可以是一个质数,比如取大于capacity的最小质数,这样可以减少冲突。
或者,可能更简单的方式是,用双向链表维护访问顺序,同时使用一个动态数组或指针数组来作为哈希表,其中每个元素指向对应的链表节点。但这样当key的范围不确定时,无法直接使用数组。所以必须使用哈希表结构。
综上,可能需要自己实现一个哈希表结构,处理key到链表节点的映射。这可能比较复杂。或者,是否有其他方法?
或者,可以简化问题,假设每个节点的key是唯一的,并且哈希表的实现可以足够高效。比如,在LRU缓存中,每个节点的key是唯一的,因此可以用一个固定大小的数组,每个数组元素是一个链表头,来处理冲突。每次查找时,先计算哈希值,然后在对应的链表中查找节点。这样,在平均情况下,每个链表的长度较短,可以视为O(1)的时间。
现在,我需要考虑如何具体实现这个结构。假设哈希表的每个bucket是一个链表,每个节点保存key和对应的链表节点指针。当get操作时,先通过哈希函数找到对应的bucket,然后遍历该bucket中的链表,找到对应的key,然后获取对应的节点指针,并将该节点移动到双向链表的头部。
同样,put操作时,如果key存在,则更新value并移动节点到头部;如果不存在,则创建新节点,插入到双向链表的头部,并将该节点添加到哈希表中。如果超过容量,则删除双向链表的尾部节点,并从哈希表中删除对应的key。
这似乎可行。但具体在C语言中如何实现呢?
首先,定义双向链表节点的结构体:
struct DLinkedNode {
int key;
int value;
struct DLinkedNode* prev;
struct DLinkedNode* next;
};
然后,LRUCache的结构体需要包含当前大小、容量、哈希表,以及双向链表的头尾指针。例如:
typedef struct {
int capacity;
int size;
struct DLinkedNode* head; // 哨兵头节点
struct DLinkedNode* tail; // 哨兵尾节点
// 哈希表,key到节点的映射
// 这里需要自己实现哈希表,比如一个数组,每个元素是链表头
// 这里假设哈希表的结构是哈希节点组成的链表,每个哈希节点保存key和对应的DLinkedNode指针
struct HashNode** hashTable;
int hashSize; // 哈希表的大小,比如取一个质数
} LRUCache;
但是,这样的HashNode结构需要单独定义:
struct HashNode {
int key;
struct DLinkedNode* node;
struct HashNode* next;
};
然后,哈希表的每个bucket是一个HashNode的链表。当需要查找某个key时,先计算哈希值,找到对应的bucket,然后遍历该bucket的链表,找到对应的HashNode,取出DLinkedNode指针。
这可能比较麻烦,但可以实现。
或者,是否可以将哈希表直接存储为指向DLinkedNode的指针?比如,使用开放寻址法,每个键值对直接存在哈希表的槽中。但如何处理冲突?
或者,是否可以使用一个简单的数组,每个元素是一个指向DLinkedNode的指针,然后通过哈希函数将key映射到数组的索引。例如,假设数组的大小足够大,或者动态调整。这可能会有冲突的问题,但可以处理。
不过,在C语言中,实现一个高效的哈希表确实比较麻烦。或许,为了简化代码,可以采用其他方式,比如用一个固定大小的哈希表数组,每个元素是一个指针,指向对应的DLinkedNode。或者,对于本题,因为capacity是给定的,我们可以将哈希表的大小设为capacity,或者更大的值,以减少冲突的可能性。例如,使用一个较大的质数作为哈希表的大小,然后对于每个key,使用取模运算得到哈希索引。
例如,定义哈希表的结构:
struct HashEntry {
int key;
struct DLinkedNode *node;
UT_hash_handle hh; // 假设使用uthash库
};
但是,如果用户不允许使用第三方库,那么必须自己实现哈希表。这可能比较复杂。
或者,可以换一种思路,使用双向链表来维护访问顺序,而用数组或者动态数组来存储每个key对应的节点指针。但这样,如果key的范围很大,数组会非常大,不可行。所以,回到哈希表的思路。
可能,这里为了简化问题,可以假设哈希表足够大,并且使用开放寻址法处理冲突。例如,哈希表的容量设置为两倍的capacity,这样可以减少冲突的概率。或者,使用线性探测来处理冲突。
但实现起来代码量可能较大。因此,或许我应该采用链表法来处理冲突,每个bucket是一个链表。
现在,关于双向链表的操作。需要维护一个最近访问的顺序,头部是最新访问的节点,尾部是最久未使用的节点。每次访问节点时,将其移动到头部。当需要添加新节点时,插入到头部。当容量满了,删除尾部节点,并同时从哈希表中删除对应的key。
那么,双向链表需要支持快速的插入头部和删除尾部的操作,这可以通过维护头尾哨兵节点来简化操作。例如,初始化时,head和tail都是哨兵节点,它们的next和prev互相指向对方。这样,插入节点到头部,只需要在head的后面插入,而尾部节点就是tail->prev。
现在,回到哈希表的实现。假设我们使用链表法处理冲突,每个bucket是一个链表,每个节点保存key和对应的DLinkedNode指针。那么,当需要查找某个key时,先计算哈希值,找到对应的bucket,然后遍历该链表查找key。如果存在,则返回对应的DLinkedNode指针;否则返回NULL。
这可能的时间复杂度取决于哈希表的负载因子。如果哈希表的bucket数量足够大,比如是capacity的两倍,那么每个链表的平均长度较低,可以视为O(1)的时间。
现在,具体实现步骤如下:
1. 定义双向链表节点结构体。
2. 定义LRUCache结构体,包含capacity、当前size、哈希表、双向链表的头尾指针。
3. 实现哈希表的创建、插入、查找、删除操作。
4. 实现LRUCache的初始化函数LRUCacheCreate。
5. 实现get和put函数。
关于哈希函数的选择,可以选择简单的取模运算。例如,哈希表的bucket数量可以是capacity的两倍,或者一个质数,比如取大于capacity的最小质数。例如,假设哈希表的大小是1024,或者动态根据capacity调整。但为了简化,可以固定哈希表的大小为一个足够大的质数,比如10007,或者让哈希表的bucket数量等于capacity的某个倍数。或者,在创建LRUCache时,根据capacity动态确定哈希表的大小。
这里,为了代码的简单,假设哈希表的大小是capacity的两倍,或者直接等于capacity。例如,在LRUCache结构体中,有一个哈希表数组,每个元素是一个链表头节点。但动态数组在C中处理起来比较麻烦,所以可能需要使用指针数组。或者,在创建LRUCache时,动态分配哈希表的内存。
或者,可以简化哈希表的实现,使用一个动态的链表数组。例如,哈希表的每个bucket是一个链表,每个节点保存key和对应的DLinkedNode指针。
但是,这样的实现可能需要较多的代码。例如,当要查找key时,需要:
1. 计算哈希值h = key % hashSize。
2. 遍历哈希表[h]处的链表,查找是否有节点的key等于目标key。
3. 如果找到,返回对应的DLinkedNode指针;否则返回NULL。
同样的,当插入一个新的key时,计算哈希值,并在对应的链表中插入一个节点。当删除某个key时,同样需要找到对应的链表中的节点并删除。
这可能比较麻烦,但必须实现。
现在,我需要考虑如何具体编写这些函数。
首先,定义结构体:
// 双向链表节点
typedef struct DLinkedNode {
int key;
int value;
struct DLinkedNode* prev;
struct DLinkedNode* next;
} DLinkedNode;
// 哈希表节点,用于处理冲突的链表
typedef struct HashNode {
int key;
DLinkedNode* cacheNode;
struct HashNode* next;
} HashNode;
// LRUCache结构体
typedef struct {
int capacity;
int size;
DLinkedNode* head; // 哨兵头节点
DLinkedNode* tail; // 哨兵尾节点
HashNode** hashTable; // 哈希表数组,每个元素是HashNode指针
int hashSize; // 哈希表的大小,例如取一个质数
} LRUCache;
然后,LRUCache的初始化函数需要创建head和tail哨兵节点,并初始化哈希表。
例如:
LRUCache* lRUCacheCreate(int capacity) {
LRUCache* cache = (LRUCache*)malloc(sizeof(LRUCache));
cache->capacity = capacity;
cache->size = 0;
// 初始化双向链表
cache->head = (DLinkedNode*)malloc(sizeof(DLinkedNode));
cache->tail = (DLinkedNode*)malloc(sizeof(DLinkedNode));
cache->head->prev = NULL;
cache->head->next = cache->tail;
cache->tail->prev = cache->head;
cache->tail->next = NULL;
// 初始化哈希表,这里假设哈希表的大小为capacity的两倍,比如2*capacity
cache->hashSize = capacity * 2;
cache->hashTable = (HashNode**)calloc(cache->hashSize, sizeof(HashNode*));
return cache;
}
这里,哈希表的大小设为capacity的两倍,以减少冲突的可能性。初始化为calloc,每个元素初始化为NULL。
接下来,哈希表的查找函数。例如,查找key对应的DLinkedNode:
DLinkedNode* findInHashTable(LRUCache* obj, int key) {
int idx = key % obj->hashSize;
HashNode* entry = obj->hashTable[idx];
while (entry != NULL) {
if (entry->key == key) {
return entry->cacheNode;
}
entry = entry->next;
}
return NULL;
}
然后,当需要将某个key添加到哈希表中时,需要创建HashNode并插入到对应的链表中:
void insertIntoHashTable(LRUCache* obj, int key, DLinkedNode* node) {
int idx = key % obj->hashSize;
HashNode* newEntry = (HashNode*)malloc(sizeof(HashNode));
newEntry->key = key;
newEntry->cacheNode = node;
newEntry->next = obj->hashTable[idx];
obj->hashTable[idx] = newEntry;
}
当需要从哈希表中删除某个key时:
void removeFromHashTable(LRUCache* obj, int key) {
int idx = key % obj->hashSize;
HashNode* curr = obj->hashTable[idx];
HashNode* prev = NULL;
while (curr != NULL) {
if (curr->key == key) {
if (prev == NULL) {
obj->hashTable[idx] = curr->next;
} else {
prev->next = curr->next;
}
free(curr);
return;
}
prev = curr;
curr = curr->next;
}
}
这样,哈希表的操作基本实现。
接下来,双向链表的操作。当需要将某个节点移动到头部时,需要先将其从链表中断开,然后插入到head之后。
例如,函数:
// 将节点移动到头部
void moveToHead(LRUCache* obj, DLinkedNode* node) {
// 先从链表中移除节点
node->prev->next = node->next;
node->next->prev = node->prev;
// 插入到head之后
node->prev = obj->head;
node->next = obj->head->next;
obj->head->next->prev = node;
obj->head->next = node;
}
// 添加新节点到头部
void addToHead(LRUCache* obj, DLinkedNode* node) {
node->prev = obj->head;
node->next = obj->head->next;
obj->head->next->prev = node;
obj->head->next = node;
}
// 删除尾部节点
DLinkedNode* removeTail(LRUCache* obj) {
DLinkedNode* node = obj->tail->prev;
node->prev->next = obj->tail;
obj->tail->prev = node->prev;
return node;
}
然后,get函数的实现:
int lRUCacheGet(LRUCache* obj, int key) {
DLinkedNode* node = findInHashTable(obj, key);
if (node == NULL) {
return -1;
}
// 移动到头部
moveToHead(obj, node);
return node->value;
}
put函数的实现:
void lRUCachePut(LRUCache* obj, int key, int value) {
DLinkedNode* node = findInHashTable(obj, key);
if (node != NULL) {
// 存在,更新值,并移动到头部
node->value = value;
moveToHead(obj, node);
} else {
// 不存在,创建新节点
DLinkedNode* newNode = (DLinkedNode*)malloc(sizeof(DLinkedNode));
newNode->key = key;
newNode->value = value;
// 插入到哈希表
insertIntoHashTable(obj, key, newNode);
// 添加到链表头部
addToHead(obj, newNode);
obj->size++;
// 如果超过容量,删除尾部节点
if (obj->size > obj->capacity) {
DLinkedNode* tailNode = removeTail(obj);
// 从哈希表中删除对应的key
removeFromHashTable(obj, tailNode->key);
free(tailNode);
obj->size--;
}
}
}
这样,可能已经完成了大部分的代码。但需要注意内存管理的问题,例如在删除节点时,要同时从哈希表中删除对应的条目,并释放内存。
但这里可能存在一个问题:当插入一个新节点时,如果哈希表中已经存在相同的key(虽然根据LRUCache的put逻辑,这种情况应该已经被处理,即如果key存在,则更新值),所以findInHashTable返回NULL时才创建新节点。因此,在put函数中的else分支是正确的。
另外,在哈希表的处理中,insertIntoHashTable函数会为每个新节点的key添加一个HashNode到哈希表中。当key已经存在时,put函数会先处理,所以不会出现重复。
但需要注意,当更新存在的节点时,是否要更新哈希表?不需要,因为HashNode中的key和DLinkedNode中的key是一致的,当key存在时,直接更新DLinkedNode的值即可。
现在,考虑测试情况。例如,当capacity为2,put(1,1),put(2,2),get(1)返回1,此时再put(3,3),导致key 2被淘汰。get(2)返回-1。
但是,这里可能存在一个问题:当removeTail的时候,获取的是tailNode,但此时Hash表中可能还存在对应的key。因此,需要调用removeFromHashTable来删除哈希表中的对应项。这在代码中已经处理。
现在,考虑哈希冲突的情况。例如,两个不同的key可能被哈希到同一个bucket中。当需要删除其中一个时,哈希表的remove函数能够正确找到对应的HashNode并删除。
综上,这样的实现应该满足O(1)的时间复杂度,因为哈希表的查找、插入、删除平均是O(1),双向链表的操作也是O(1)。
不过,在C语言中,这样的实现可能比较复杂,特别是哈希表的部分。有没有更简便的方法?
另一种思路是,因为capacity是已知的,可以预先分配一个足够大的数组,直接以key为索引。但这样当key的范围很大时会占用过多内存,不可行。所以,必须使用哈希表。
综上,代码的大体结构应该是正确的。但需要检查是否有错误。
例如,当调用findInHashTable时,返回的是DLinkedNode指针,正确。当移动节点到头部时,是否正确处理了链表指针。例如,moveToHead函数中的操作是否正确?
假设原来的链表结构是 head <-> node1 <-> node2 <-> tail。当移动node1到头部,操作应该是将其从原位置移除,然后插入到head后面。例如:
node1->prev = head;
node1->next = head->next;
head->next->prev = node1;
head->next = node1;
这应该是正确的。
此外,当添加新节点时,使用addToHead函数,正确插入到head之后。
当删除尾部节点时,removeTail函数返回的是tail的前驱节点,然后将其从链表中删除。
此时,需要释放该节点的内存,并从哈希表中删除对应的key。这部分是否正确?
是的,在put函数中,当超过容量时,删除tailNode,并从哈希表中移除对应的key,并free该节点。
现在,关于哈希表的内存管理:每个HashNode在插入时被创建,当对应的DLinkedNode被删除时,HashNode也应该被删除。例如,在removeFromHashTable函数中,会找到对应的HashNode并释放内存。
现在,有没有可能内存泄漏?例如,当调用removeTail时,是否忘记释放HashNode?
在removeFromHashTable中,找到对应的HashNode,并free掉。而DLinkedNode在removeTail之后被free。所以这部分是正确的。
此外,在LRUCache的销毁函数中,应该释放所有分配的内存,包括哈希表中的所有HashNode,双向链表的所有节点,以及LRUCache结构体本身。但用户可能没有要求实现销毁函数,但为了完整性可能需要补充。
但题目中的要求只是实现LRUCache类,即结构体和对应的函数,可能不需要处理销毁部分。但用户给出的问题中的C语言代码可能需要释放内存,否则会有内存泄漏。但在在线编程题中,可能不会检查内存泄漏,只要功能正确即可。不过,正确的实现应该处理这些。
例如,当用户调用lRUCacheFree时,需要释放所有内存:
void lRUCacheFree(LRUCache* obj) {
// 释放所有DLinkedNode
DLinkedNode* curr = obj->head->next;
while (curr != obj->tail) {
DLinkedNode* temp = curr;
curr = curr->next;
free(temp);
}
free(obj->head);
free(obj->tail);
// 释放哈希表中的所有HashNode
for (int i = 0; i < obj->hashSize; i++) {
HashNode* entry = obj->hashTable[i];
while (entry != NULL) {
HashNode* temp = entry;
entry = entry->next;
free(temp);
}
}
free(obj->hashTable);
free(obj);
}
这可能比较复杂,但需要确保所有动态分配的内存都被释放。
现在,回到原来的问题,用户需要的是用C语言实现代码。综上,整个代码的结构大致如此。但需要编写完整的代码,包括所有函数和结构体定义。
但可能存在一些错误,比如哈希表的实现是否正确,或者双向链表的操作是否正确。
例如,在findInHashTable函数中,返回的是DLinkedNode指针,而哈希表的每个HashNode保存的是该指针。当调用get函数时,正确返回该节点的value,并移动节点到头部。
在put函数中,当插入新节点时,是否在哈希表中正确添加了对应的HashNode?
是的,insertIntoHashTable函数被调用,将key和node添加到哈希表中。
当删除节点时,removeFromHashTable被调用,正确删除了对应的key。
现在,测试代码是否正常工作。例如,以下测试用例:
capacity = 2
put(1,1); put(2,2); get(1) → 1; put(3,3); get(2) → -1; get(3) →3; put(4,4); get(1)→-1; get(3)→3; get(4)→4;
此时,在put(3,3)时,容量已满,需要删除最久未使用的节点。此时,链表中的顺序是 1被访问过,所以最近访问的是1,最久未使用的是2。所以,插入3时,删除2。接着,put(4,4)时,容量为2,此时链表中的最近访问是3,最久未使用的是1,所以删除1,插入4。get(1)返回-1。
这样,代码是否能正确处理?
在代码中,当插入新节点时,添加它到头部。当put(1,1)和put(2,2)后,链表顺序是 head <-> 2 <-> 1 <-> tail?或者头插入方式,所以插入顺序是put(1,1)时添加到头部,所以链表是 head <->1<-> tail。然后put(2,2)时,添加到头部,链表变为 head <->2<->1<-> tail。此时,get(1)将移动1到头部,链表变为 head <->1<->2<->tail。此时,当put(3,3)时,容量已满,所以删除尾部节点,即2。哈希表中删除key 2,链表变为 head <->1<->3<->tail。这样,后续get(2)返回-1。是的,这应该正确。
综上,代码逻辑正确。
现在,将上述思路转化为C代码。由于代码较长,需要注意所有函数的正确性。
可能的代码实现如下:
首先,包含必要的头文件:
#include <stdio.h>
#include <stdlib.h>
然后,结构体定义:
typedef struct DLinkedNode {
int key;
int value;
struct DLinkedNode* prev;
struct DLinkedNode* next;
} DLinkedNode;
typedef struct HashNode {
int key;
DLinkedNode* cacheNode;
struct HashNode* next;
} HashNode;
typedef struct {
int capacity;
int size;
DLinkedNode* head;
DLinkedNode* tail;
HashNode** hashTable;
int hashSize;
} LRUCache;
然后,LRUCache的创建函数:
LRUCache* lRUCacheCreate(int capacity) {
LRUCache* cache = (LRUCache*)malloc(sizeof(LRUCache));
cache->capacity = capacity;
cache->size = 0;
cache->head = (DLinkedNode*)malloc(sizeof(DLinkedNode));
cache->tail = (DLinkedNode*)malloc(sizeof(DLinkedNode));
cache->head->prev = NULL;
cache->head->next = cache->tail;
cache->tail->prev = cache->head;
cache->tail->next = NULL;
// 设置哈希表的大小为capacity的两倍,或者选择一个质数,这里简化处理
cache->hashSize = capacity * 2;
cache->hashTable = (HashNode**)calloc(cache->hashSize, sizeof(HashNode*));
return cache;
}
然后,辅助函数:
DLinkedNode* findInHashTable(LRUCache* obj, int key) {
int idx = key % obj->hashSize;
HashNode* entry = obj->hashTable[idx];
while (entry != NULL) {
if (entry->key == key) {
return entry->cacheNode;
}
entry = entry->next;
}
return NULL;
}
void insertIntoHashTable(LRUCache* obj, int key, DLinkedNode* node) {
int idx = key % obj->hashSize;
HashNode* newEntry = (HashNode*)malloc(sizeof(HashNode));
newEntry->key = key;
newEntry->cacheNode = node;
newEntry->next = obj->hashTable[idx];
obj->hashTable[idx] = newEntry;
}
void removeFromHashTable(LRUCache* obj, int key) {
int idx = key % obj->hashSize;
HashNode* curr = obj->hashTable[idx];
HashNode* prev = NULL;
while (curr != NULL) {
if (curr->key == key) {
if (prev == NULL) {
obj->hashTable[idx] = curr->next;
} else {
prev->next = curr->next;
}
free(curr);
return;
}
prev = curr;
curr = curr->next;
}
}
void moveToHead(LRUCache* obj, DLinkedNode* node) {
// 先断开node
node->prev->next = node->next;
node->next->prev = node->prev;
// 插入到head后面
node->prev = obj->head;
node->next = obj->head->next;
obj->head->next->prev = node;
obj->head->next = node;
}
void addToHead(LRUCache* obj, DLinkedNode* node) {
node->prev = obj->head;
node->next = obj->head->next;
obj->head->next->prev = node;
obj->head->next = node;
}
DLinkedNode* removeTail(LRUCache* obj) {
DLinkedNode* node = obj->tail->prev;
node->prev->next = obj->tail;
obj->tail->prev = node->prev;
return node;
}
实现get函数:
int lRUCacheGet(LRUCache* obj, int key) {
DLinkedNode* node = findInHashTable(obj, key);
if (node == NULL) {
return -1;
}
moveToHead(obj, node);
return node->value;
}
实现put函数:
void lRUCachePut(LRUCache* obj, int key, int value) {
DLinkedNode* node = findInHashTable(obj, key);
if (node != NULL) {
node->value = value;
moveToHead(obj, node);
} else {
DLinkedNode* newNode = (DLinkedNode*)malloc(sizeof(DLinkedNode));
newNode->key = key;
newNode->value = value;
insertIntoHashTable(obj, key, newNode);
addToHead(obj, newNode);
obj->size++;
if (obj->size > obj->capacity) {
DLinkedNode* tailNode = removeTail(obj);
removeFromHashTable(obj, tailNode->key);
free(tailNode);
obj->size--;
}
}
}
销毁函数:
void lRUCacheFree(LRUCache* obj) {
// 释放所有DLinkedNode
DLinkedNode* curr = obj->head->next;
while (curr != obj->tail) {
DLinkedNode* tmp = curr;
curr = curr->next;
free(tmp);
}
free(obj->head);
free(obj->tail);
// 释放哈希表
for (int i = 0; i < obj->hashSize; i++) {
HashNode* entry = obj->hashTable[i];
while (entry != NULL) {
HashNode* tmp = entry;
entry = entry->next;
free(tmp);
}
}
free(obj->hashTable);
free(obj);
}
这样,完整的代码应该能够实现LRU缓存的功能。需要注意的是,哈希表的实现可能会影响性能,但根据平均情况,假设哈希表的冲突足够少,可以满足O(1)的时间复杂度要求。
可能存在的改进点:
1. 哈希表的大小可以选择一个更大的质数,以减少冲突的概率。
2. 在哈希表中,可以使用更高效的冲突处理方法,比如开放寻址法,但实现可能更复杂。
3. 可以优化moveToHead函数,避免在节点已经是头部的情况下进行不必要的操作,但通常这样的优化对整体性能影响不大。
综上,这个实现应该满足题目的要求。</think>以下是使用C语言实现的LRU缓存数据结构,结合哈希表和双向链表以达到O(1)时间复杂度:
```c
#include <stdio.h>
#include <stdlib.h>
// 双向链表节点
typedef struct DLinkedNode {
int key;
int value;
struct DLinkedNode* prev;
struct DLinkedNode* next;
} DLinkedNode;
// 哈希表节点(用于处理冲突)
typedef struct HashNode {
int key;
DLinkedNode* cacheNode;
struct HashNode* next;
} HashNode;
// LRU缓存结构体
typedef struct {
int capacity;
int size;
DLinkedNode* head; // 哨兵头节点
DLinkedNode* tail; // 哨兵尾节点
HashNode** hashTable; // 哈希表数组
int hashSize; // 哈希表大小
} LRUCache;
// 创建LRU缓存
LRUCache* lRUCacheCreate(int capacity) {
LRUCache* cache = (LRUCache*)malloc(sizeof(LRUCache));
cache->capacity = capacity;
cache->size = 0;
// 初始化双向链表
cache->head = (DLinkedNode*)malloc(sizeof(DLinkedNode));
cache->tail = (DLinkedNode*)malloc(sizeof(DLinkedNode));
cache->head->prev = NULL;
cache->head->next = cache->tail;
cache->tail->prev = cache->head;
cache->tail->next = NULL;
// 初始化哈希表
cache->hashSize = capacity * 2; // 简化处理哈希表大小
cache->hashTable = (HashNode**)calloc(cache->hashSize, sizeof(HashNode*));
return cache;
}
// 哈希表辅助函数
DLinkedNode* findInHashTable(LRUCache* obj, int key) {
int idx = key % obj->hashSize;
HashNode* entry = obj->hashTable[idx];
while (entry != NULL) {
if (entry->key == key) {
return entry->cacheNode;
}
entry = entry->next;
}
return NULL;
}
void insertIntoHashTable(LRUCache* obj, int key, DLinkedNode* node) {
int idx = key % obj->hashSize;
HashNode* newEntry = (HashNode*)malloc(sizeof(HashNode));
newEntry->key = key;
newEntry->cacheNode = node;
newEntry->next = obj->hashTable[idx];
obj->hashTable[idx] = newEntry;
}
void removeFromHashTable(LRUCache* obj, int key) {
int idx = key % obj->hashSize;
HashNode* curr = obj->hashTable[idx];
HashNode* prev = NULL;
while (curr != NULL) {
if (curr->key == key) {
if (prev == NULL) {
obj->hashTable[idx] = curr->next;
} else {
prev->next = curr->next;
}
free(curr);
return;
}
prev = curr;
curr = curr->next;
}
}
// 链表操作辅助函数
void moveToHead(LRUCache* obj, DLinkedNode* node) {
// 断开原链接
node->prev->next = node->next;
node->next->prev = node->prev;
// 插入头部
node->prev = obj->head;
node->next = obj->head->next;
obj->head->next->prev = node;
obj->head->next = node;
}
void addToHead(LRUCache* obj, DLinkedNode* node) {
node->prev = obj->head;
node->next = obj->head->next;
obj->head->next->prev = node;
obj->head->next = node;
}
DLinkedNode* removeTail(LRUCache* obj) {
DLinkedNode* node = obj->tail->prev;
node->prev->next = obj->tail;
obj->tail->prev = node->prev;
return node;
}
// 获取缓存值
int lRUCacheGet(LRUCache* obj, int key) {
DLinkedNode* node = findInHashTable(obj, key);
if (node == NULL) return -1;
moveToHead(obj, node); // 更新为最近使用
return node->value;
}
// 插入缓存值
void lRUCachePut(LRUCache* obj, int key, int value) {
DLinkedNode* node = findInHashTable(obj, key);
if (node != NULL) { // 存在则更新
node->value = value;
moveToHead(obj, node);
} else { // 不存在则新建
DLinkedNode* newNode = (DLinkedNode*)malloc(sizeof(DLinkedNode));
newNode->key = key;
newNode->value = value;
insertIntoHashTable(obj, key, newNode);
addToHead(obj, newNode);
obj->size++;
// 超过容量则淘汰尾部
if (obj->size > obj->capacity) {
DLinkedNode* tailNode = removeTail(obj);
removeFromHashTable(obj, tailNode->key);
free(tailNode);
obj->size--;
}
}
}
// 释放缓存
void lRUCacheFree(LRUCache* obj) {
// 释放链表节点
DLinkedNode* curr = obj->head->next;
while (curr != obj->tail) {
DLinkedNode* tmp = curr;
curr = curr->next;
free(tmp);
}
free(obj->head);
free(obj->tail);
// 释放哈希表
for (int i = 0; i < obj->hashSize; i++) {
HashNode* entry = obj->hashTable[i];
while (entry != NULL) {
HashNode* tmp = entry;
entry = entry->next;
free(tmp);
}
}
free(obj->hashTable);
free(obj);
}
```
### 实现说明
1. **数据结构设计**:
- **双向链表**:维护缓存项的访问顺序,头部为最近使用,尾部为最久未使用。
- **哈希表**:通过链表法处理冲突,实现O(1)时间复杂度的键值查找。
2. **核心操作**:
- **`lRUCacheGet`**:通过哈希表查找键,若存在则将节点移至链表头部。
- **`lRUCachePut`**:若键存在则更新值并移至头部;若不存在则创建新节点。若容量超限,则删除链表尾部节点。
3. **时间复杂度**:
- **哈希表操作**:平均O(1)时间完成查找、插入和删除。
- **链表操作**:移动节点、插入头部、删除尾部均只需O(1)时间。
### 使用示例
```c
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); // 淘汰键 2
printf("%d\n", lRUCacheGet(cache, 2)); // 返回 -1
lRUCacheFree(cache);
return 0;
}
```
该实现通过哈希表与双向链表的结合,高效地满足了LRU缓存的O(1)操作要求。