题目:
题解:
/*
数值链表的节点定义。
*/
typedef struct ValueListNode_s
{
int key;
int value;
int counter;
struct ValueListNode_s *prev;
struct ValueListNode_s *next;
}
ValueListNode;
/*
计数链表的节点定义。
其中,head是数值链表的头节点,对应的是最新的数值节点。
环形链表,head->prev实际就是tail,对应的就是最久未使用的节点。
*/
typedef struct CounterListNode_s
{
ValueListNode *head;
struct CounterListNode_s *prev;
struct CounterListNode_s *next;
}
CounterListNode;
/*
对象结构定义。
capacity: 总的容量。
currentCounter: 当前已有的key的数量。
keyHash: key的哈希数组,为空表示这个key对应数值不存在。
counterHash: counter的哈希数组,为空表示这个counter对应的链表不存在。
head: 计数链表的头节点。
*/
typedef struct
{
int capacity;
int currentCounter;
ValueListNode **keyHash;
CounterListNode **counterHash;
CounterListNode *head;
}
LFUCache;
/*
几个自定义函数的声明,具体实现见下。
*/
extern void removeValueNode(CounterListNode *counterNode, ValueListNode *valueNode);
extern void insertValueNode(CounterListNode *counterNode, ValueListNode *valueNode);
extern void removeCounterNode(LFUCache *obj, CounterListNode *counterNode);
extern void insertCounterNode(LFUCache *obj, CounterListNode *counterPrev, CounterListNode *counterNode);
/*
创建对象。
*/
LFUCache *lFUCacheCreate(int capacity)
{
LFUCache *obj = (LFUCache *)malloc(sizeof(LFUCache));
/* 总容量就等于入参capacity,当前已有的key的数量初始化为0。 */
obj->capacity = capacity;
obj->currentCounter = 0;
/* key的取值范围是[0, 10^5],共100001个。用calloc代替malloc,即包含了初始化为空的步骤。 */
obj->keyHash = (ValueListNode **)calloc(100001, sizeof(ValueListNode *));
/* 题目给的操作次数上限是2*10^5。同上,用calloc代替malloc,包含了初始化为空的步骤。 */
obj->counterHash = (CounterListNode **)calloc(200001, sizeof(CounterListNode *));
/* 刚开始时,计数链表为空。 */
obj->head = NULL;
return obj;
}
/*
获取指定key的数值。
value: 想要获取key对应的数值,初始化为-1,假如获取不到,就返回这个-1。
valueNode: 从keyHash中直接获取的数值链表节点。
counterNode: 在计数加一之前,这个数值节点当前所处的计数链表。
counterNew: 在计数加一之后,这个数值节点想要加入的新计数链表。
*/
int lFUCacheGet(LFUCache *obj, int key)
{
int value = -1;
ValueListNode *valueNode = obj->keyHash[key];
CounterListNode *counterNode = NULL, *counterNew = NULL;
/* 对应的key存在数值时,才需要返回其数值,否则返回-1。 */
if(NULL != valueNode)
{
/* 要返回的数值。 */
value = valueNode->value;
/* 这个节点当前在哪一个计数链表节点中。 */
counterNode = obj->counterHash[valueNode->counter];
/* 数值的计数加一。以及计数加一之后,它想要加入的新的计数链表节点。 */
valueNode->counter++;
counterNew = obj->counterHash[valueNode->counter];
/* 把数值节点从旧的链表中移除。 */
removeValueNode(counterNode, valueNode);
/* 如果这个新的计数节点还不存在,则新建一个节点。 */
if(NULL == counterNew)
{
counterNew = (CounterListNode *)calloc(1, sizeof(CounterListNode));
obj->counterHash[valueNode->counter] = counterNew;
/* 新建计数节点,加到counterNode的后方。 */
insertCounterNode(obj, counterNode, counterNew);
}
/* 如果旧的计数节点中的数值链表变为空,则旧的计数节点也需要从计数链表中移除。 */
if(NULL == counterNode->head)
{
removeCounterNode(obj, counterNode);
free(counterNode);
obj->counterHash[valueNode->counter - 1] = NULL;
}
/* 把数值节点加入到新的链表中。 */
insertValueNode(counterNew, valueNode);
}
return value;
}
/*
赋值指定key的数值。
keyRemove: 要被移除的键值。
valueNode: 指定的key对应的数值节点。
valueRemove: 可能被移除的数值节点。
counterNode: 在计数加一之前,这个数值节点当前所处的计数链表。
counterNew: 在计数加一之后,这个数值节点想要加入的新计数链表。
*/
void lFUCachePut(LFUCache *obj, int key, int value)
{
int keyRemove = 0;
ValueListNode *valueNode = obj->keyHash[key], *valueRemove = NULL;
CounterListNode *counterNode = NULL, *counterNew = NULL;
/* 总容量为0的话,什么都不需要做。 */
if(0 == obj->capacity)
{
return;
}
/* 如果这个key值已经存在,则修改其数值。 */
if(NULL != valueNode)
{
/* 修改新的数值。 */
valueNode->value = value;
/* 这个节点当前在哪一个计数链表节点中。 */
counterNode = obj->counterHash[valueNode->counter];
/* 数值的计数加一。以及计数加一之后,它想要加入的新的计数链表节点。 */
valueNode->counter++;
counterNew = obj->counterHash[valueNode->counter];
/* 把数值节点从旧的链表中移除。 */
removeValueNode(counterNode, valueNode);
/* 如果这个新的计数节点还不存在,则新建一个节点。 */
if(NULL == counterNew)
{
counterNew = (CounterListNode *)calloc(1, sizeof(CounterListNode));
obj->counterHash[valueNode->counter] = counterNew;
/* 新建计数节点,加到counterNode的后方。 */
insertCounterNode(obj, counterNode, counterNew);
}
/* 如果旧的计数节点中的数值链表变为空,则旧的计数节点也需要从计数链表中移除。 */
if(NULL == counterNode->head)
{
removeCounterNode(obj, counterNode);
free(counterNode);
obj->counterHash[valueNode->counter - 1] = NULL;
}
/* 把数值节点加入到新的链表中。 */
insertValueNode(counterNew, valueNode);
}
/* 否则,新建一个键值。 */
else
{
/* 如果没有满总量,则数量加一。 */
if(obj->capacity > obj->currentCounter)
{
obj->currentCounter++;
}
/* 否则,先把最近最久未使用的键移除。 */
else
{
/* 要删除的数值节点所在的计数节点,一定是计数最少的那个counterNode,即头节点。 */
counterNode = obj->head;
/* 要被移除的节点,是数值链表的尾节点。 */
valueRemove = counterNode->head->prev;
keyRemove = valueRemove->key;
/* 把它从链表中移除。 */
removeValueNode(counterNode, valueRemove);
/* 如果计数节点中的数值链表变成空,则也移除这个计数节点。 */
if(NULL == counterNode->head)
{
removeCounterNode(obj, counterNode);
free(counterNode);
obj->counterHash[valueRemove->counter] = NULL;
}
free(valueRemove);
obj->keyHash[keyRemove] = NULL;
}
/* 新建一个数值节点。 */
valueNode = (ValueListNode *)calloc(1, sizeof(ValueListNode));
valueNode->key = key;
valueNode->value = value;
valueNode->counter = 1;
obj->keyHash[key] = valueNode;
/* 要新加入的链表。新出现的数值,计数肯定是1。 */
counterNew = obj->counterHash[1];
/* 如果这个计数节点还不存在,则新建一个。 */
if(NULL == counterNew)
{
counterNew = (CounterListNode *)calloc(1, sizeof(CounterListNode));
obj->counterHash[1] = counterNew;
/* counter为1的计数节点,肯定是加到头部的。 */
insertCounterNode(obj, NULL, counterNew);
}
/* 把数值节点加入到新的链表中。 */
insertValueNode(counterNew, valueNode);
}
return;
}
/*
释放对象。
*/
void lFUCacheFree(LFUCache *obj)
{
CounterListNode *counterNode = obj->head, *counterNext = NULL;
ValueListNode *valueNode = NULL, *valueNext = NULL;
/* 逐个释放计数链表的每个节点。 */
while(NULL != counterNode)
{
counterNext = counterNode->next;
/* 释放每个计数链表节点下面的数值链表。
环形链表的循环,使用do、while语句。 */
valueNode = counterNode->head;
do
{
valueNext = valueNode->next;
free(valueNode);
valueNode = valueNext;
}
while(counterNode->head != valueNode);
free(counterNode);
counterNode = counterNext;
}
/* 释放key的哈希数组。 */
free(obj->keyHash);
/* 释放counter的哈希数组。 */
free(obj->counterHash);
/* 释放对象。 */
free(obj);
return;
}
/*
几个自定义函数的具体实现。
主要是双向链表、双向循环链表的节点添加、删除的操作,保证操作前后仍然是双向链表、双向循环链表。
*/
/*
把数值节点从数值链表中删除。
*/
void removeValueNode(CounterListNode *counterNode, ValueListNode *valueNode)
{
/* 如果这个被删除节点是链表中的唯一一个,则删除之后直接为空链表。 */
if(valueNode->next == valueNode)
{
counterNode->head = NULL;
}
/* 否则把它的前后两个节点连接起来。 */
else
{
valueNode->prev->next = valueNode->next;
valueNode->next->prev = valueNode->prev;
/* 如果删掉的就是头节点,则新的头节点的位置往后挪一位。 */
if(counterNode->head == valueNode)
{
counterNode->head = valueNode->next;
}
}
return;
}
/*
把数值节点加入到数值链表头部。
*/
void insertValueNode(CounterListNode *counterNode, ValueListNode *valueNode)
{
ValueListNode *tail = NULL;
/* 如果本身是空链表,则它是其中唯一节点。 */
if(NULL == counterNode->head)
{
valueNode->prev = valueNode;
valueNode->next = valueNode;
}
/* 否则就把它插入到原来的头尾之间。 */
else
{
tail = counterNode->head->prev;
valueNode->prev = tail;
valueNode->next = counterNode->head;
counterNode->head->prev = valueNode;
tail->next = valueNode;
}
/* 它成为新的头节点。 */
counterNode->head = valueNode;
return;
}
/*
把计数节点从计数链表中删除。
*/
void removeCounterNode(LFUCache *obj, CounterListNode *counterNode)
{
/* 如果删除的本身是头节点,则头节点将变为下一个。 */
if(obj->head == counterNode)
{
obj->head = counterNode->next;
if(NULL != obj->head)
{
obj->head->prev = NULL;
}
}
/* 否则,把它的前后两个节点连起来。
不是头节点的话,prev肯定存在,next可能为空。 */
else
{
counterNode->prev->next = counterNode->next;
if(NULL != counterNode->next)
{
counterNode->next->prev = counterNode->prev;
}
}
return;
}
/*
把一个新的计数节点加入到计数链表指定节点counterPrev的后方。
如果counterPrev为空,则表示加到链表头。
*/
void insertCounterNode(LFUCache *obj, CounterListNode *counterPrev, CounterListNode *counterNode)
{
/* 如果counterPrev为空,说明是加入到头节点的位置。 */
if(NULL == counterPrev)
{
counterNode->prev = NULL;
counterNode->next = obj->head;
if(NULL != obj->head)
{
obj->head->prev = counterNode;
}
obj->head = counterNode;
}
/* 否则插入到counterPrev和counterPrev->next之间。 */
else
{
counterNode->prev = counterPrev;
counterNode->next = counterPrev->next;
if(NULL != counterPrev->next)
{
counterPrev->next->prev = counterNode;
}
counterPrev->next = counterNode;
}
return;
}