【专栏】数据结构和算法之美-链表(上):如何实现LRU缓存淘汰算法?

24 篇文章 1 订阅
24 篇文章 3 订阅

学习笔记

链表结构

单链表

示意图如下
在这里插入图片描述
结构特征:

  • 尾节点的指针域指向NULL
  • 用头节点记录链表的基地址
    行为特征:
  • 插入或删除只需要考虑相邻节点指针的改变,其时间复杂度是O(1)
  • 通过指针将零散的内存块串联起来使用
  • 随机访问第K个元素的时间复杂度是O(n), 每个节点只能找到它后面的节点,因而要找到第K个元素就需要从链表头开始一个一个往后询问直到遇到第K个,没有数组那么好的性能。
    下图演示插入或删除一个节点的操作
    在这里插入图片描述
循环链表

结构特征

  • 尾节点指针域指向头节点,形成一个环,其他特征和单链表一样,示意图如下
    在这里插入图片描述
    行为特征
  • 约瑟夫问题
  • 访问能从链表尾端直接到链表头部
双向链表

结构特征

  • 每个节点不只有一个后继指针next指向后面的节点,还有一个前驱指针pre指向前面的节点。
  • 存储同样多的数据比单链表占用更多的存储空间
    结构示意图如下
    在这里插入图片描述
    行为特征
  • 要删除节点中“值等于某个给定值”的节点,遍历查找到符合条件的节点,需要从链表头开始检查直到找到符合条件的节点,查找的时间复杂度是O(n),找到之后可以直接删除它,删除的时间复杂度是O(n),根据时间复杂度的加法法则,这种情况下的总实际那复杂度就是O(n).
  • 删除给定指针指向的节点,我们已经找到了要删除的结点,但是删除某个结点 q, 需要知道其前驱结点,但单链表并不支持直接获取前驱结点,我们还是要从头结点开始遍历链表,直到 p->next=q,说明p是q的前驱结点。这种情况下,单链表的时间复杂度仍然是O(n)。而双向链表能直接获取q的前驱结点,它的时间复杂度是O(1),更高效。
  • 用空间换时间的设计思想
链表vs数组性能比拼

从行为特征上来看,插入删除和随机访问的时间复杂度性能刚好相反,如下
在这里插入图片描述
从结构特征上来看,数组使用连续内存空间,可借助CPU缓存机制,预读数据,访问效率高,但数组大小固定,可能系统没有足够大的连续内存空间可供分配。链表使用离散的内存空间,在系统运行时获得内存和运行结束时是否内存,动态的内存分配和释放能充分利用内存从而能节省内存使用,频繁的申请和释放肯能会产生内存碎片。

实现LRU缓存淘汰算法

所谓缓存策略, 就是当缓存满了之后, 又有新数据需要加入内存时, 我们怎么从缓存中为新数据腾出空间的策略。这个权衡的过程就是所谓的缓存策略。
LRU, Least Recently Used的简写, 即近期最少使用算法。 该算法依据与程序的局部性原理, 其淘汰旧数据的策略是, 距离当前最久没有被访问过的数据应该被淘汰。

基于链表实现
/*LRU algorithm*/

#include <stdio.h>
#include <stdlib.h>

#define LOG() printf("[%d]\n", __LINE__)
#define LRU_CAPACITY 10

struct nodeSoldier{
    int number; /*the soldier's number*/
    struct nodeSoldier *next; /*point to next soldier*/
}nodeSoldier;

typedef struct nodeSoldier *pNodeSoldier;

/*insert node in the header of the linked list*/
void insertLikedlist(pNodeSoldier *head, int *pdata)
{
    pNodeSoldier pNewSoldier;
    pNodeSoldier probe = *head;
    pNewSoldier =(pNodeSoldier)malloc(sizeof(struct nodeSoldier));

    if (NULL != pNewSoldier)
    {
        pNewSoldier->number = *pdata;
        pNewSoldier->next = probe;
        *head = pNewSoldier;
    }
    else
    {
        printf("malloc error!\n");
    }
}

/*deleted matched node*/
pNodeSoldier deleteLinkedlist(pNodeSoldier head, int data)
{
    pNodeSoldier probe = head;
    pNodeSoldier preProbe = head;
    while(data != probe->number)
    {
        probe = probe->next;
    }
    /*the node probe point to*/
    if (NULL != probe)
    {
        /*delete it*/
        if (probe == head) /*is the first node*/
        {
            head = probe->next;
        }
        else
        {
            /*find the node before it*/
            preProbe = head;
            while(preProbe->next != probe)
            {
                preProbe = preProbe->next;
            }
            preProbe->next = probe->next;
        }
        free(probe);        
    }
    else 
    {
        printf("no found\n");
    }
    return (head);
}

/*Create LRU catch with the single linked list*/
/*soldier is named as 1,2,3,...capacity in turn*/
pNodeSoldier LRUCreate(const int capacity)
{
    pNodeSoldier head = NULL;
    pNodeSoldier probe = NULL;
    int counter = capacity;

    /*if length of the linked list is bigger than 1, then adding one more node*/  
    if (capacity > 1)
    {
        /*initialize the first node firstly*/
        head = (pNodeSoldier)malloc(sizeof(struct nodeSoldier));
        if(NULL != head)
        {
            head->next = NULL;
            head->number = capacity;
        }
        else 
        {
            /*malloc error*/
            printf("malloc error!\n");
        }
        /*insert the remaining nodes in the header of the linked list one by one*/
        for (; counter > 1; counter--)
        {
            probe = (pNodeSoldier)malloc(sizeof(struct nodeSoldier));
            if (NULL != probe)
            {
                probe->next = head;
                probe->number = counter - 1;
                head = probe;
            }
            else
            {
                /*malloc error*/
                 printf("malloc error!\n");
            }
        }
    }
    else if (capacity == 1)
    {
        /*only 1 node in the linked list*/
        head = (pNodeSoldier)malloc(sizeof(struct nodeSoldier));
        if(NULL != head)
        {
            head->next = NULL;
            head->number = 1;
        }
        else 
        {
            /*malloc error*/
            printf("malloc error!\n");
        }
    }
    else
    {
        printf("capacity error! it should be bigger than 0!\n");
        /*capacity error*/
    }
    return (head);
}

/*calculate the length of the catch*/
int LRUCalcuLen(pNodeSoldier const head)
{
    int counter = 0;
    pNodeSoldier probe = head;
    if (NULL != head)
    {
        while(NULL != probe)
        {
            ++counter;
            probe = probe->next;
        }
    }
    else
    {
        printf("input parameters error!\n");
        /*NULL pointer from the input parameters of the function*/
    }
    return (counter); /*cann't return the pointer variable or reference to the stack,but others'variable because the stack will be destroied when the function exit*/
}

/*output the LRU catch*/
void LRUOutput(pNodeSoldier const head)
{
    pNodeSoldier probe = head;
    printf("--------------output the linked list----------\n");
    if (NULL != head)
    {
        for(; probe != NULL; )
        {
           printf("Node's number is %d\n", probe->number);
           probe = probe->next;
        }
    }
    else
    {
         printf("input parameters error!\n");
        /*NULL pointer from the input parameters of the function*/       
    }
}

/*destroy the LRU catch*/
void LRUDestroy(pNodeSoldier head)
{
    pNodeSoldier probe = head;
    pNodeSoldier nextProbe = head;
    if (NULL != head)
    {
        for(; probe != NULL; )
        {
           nextProbe = probe->next;
           free(probe);
           probe = nextProbe;
        }
    }
    else
    {
         printf("input parameters error!\n");
        /*NULL pointer from the input parameters of the function*/       
    }
}

#if 1
pNodeSoldier LRUAlogrithm(pNodeSoldier head, int soldierNumber)
{
    pNodeSoldier preProbe = head;
    pNodeSoldier probe = head;
    pNodeSoldier pNewSoldier = NULL;
    int length = LRUCalcuLen(head);
    if (NULL != head)
    {
        /*find it in the link list*/
        for(; NULL != probe; )
        {
            if (probe->number != soldierNumber)
            {
                /*I don't find it, continue to find it until the last node probe point to*/
                probe = probe->next;
            }
            else
            {
                /*Yes, I find it, I can leave*/
                
                break;
            }
        }
        /*Link list doesn't have it, then add it in the header of the linked list*/
        if (NULL == probe)
        {
            /*Buffer is not full, then put it in the header*/
            if (LRU_CAPACITY > length)
            {
                insertLikedlist(&head, &soldierNumber);
            }
            else/*Buffer is full, then delete the last node, then put it in the header*/
            {
                
                /*find the last node*/
                probe = head;
                while(NULL != probe->next)
                {
                    probe = probe->next;                  
                }
                /*probe point to the last node,find the node before it*/
                preProbe = head;
                while (preProbe->next != probe)
                {
                    preProbe = preProbe->next;
                }
                /*delete the last node*/
                preProbe->next = NULL;
                free(probe);
                insertLikedlist(&head, &soldierNumber);
            }
        }
        else/*move it to the header of the linked list*/
        {
            
            /*find the node before it*/
            while(preProbe->next != probe)
            {
                preProbe = preProbe->next;
            }
            /*connect to the next of the node which probe point to*/
            preProbe->next = probe->next;
            /*head connect to the node which probe point to */
            probe->next = head;
            head = probe;
        }
    }
    else
    {
        /*NULL address access from input parameters of the function*/
    }
    return (head);
}
#endif

void main(void)
{
    pNodeSoldier head = NULL;
	pNodeSoldier pNewNode = NULL;
    int soldierNumber = 14;
    int length = 0;
    printf("-----------------test case 0: node's number can't zero------------------\n");
    head = LRUCreate(0);
    printf("-----------------test case 1: only one node ------------------\n");
    head = LRUCreate(1);
    LRUDestroy(head);
    printf("-----------------test case 2: big enough catch------------------\n");
    head = LRUCreate(13);
    printf("the length of the LRU is %d\n",  LRUCalcuLen(head));
    LRUOutput(head);
    printf("-----------------test case 3: insert node------------------\n");
    insertLikedlist(&head, &soldierNumber);
    LRUOutput(head);
    printf("-----------------test case 4: delete the first node------------------\n");
    head = deleteLinkedlist(head, 14);
    LRUOutput(head);
    printf("-----------------test case 5: the node deleted is the middle node: ------------------\n");
    head = deleteLinkedlist(head, 7);
    LRUOutput(head);
    printf("-----------------test case 6: the node deleted is the last node: ------------------\n");
    head = deleteLinkedlist(head, 13);
    LRUOutput(head);
	printf("-----------------test case 7: put new data in the header after deleting the last node if catch is full ------------------\n");
    head = LRUAlogrithm(head, 15);
	LRUOutput(head);
    printf("-----------------test case 8: put new data in the header if catch is not full ------------------\n");
	head = deleteLinkedlist(head, 8);
	head = deleteLinkedlist(head, 9);
	head = deleteLinkedlist(head, 10);
	head = deleteLinkedlist(head, 11);

    LRUOutput(head);
    head = LRUAlogrithm(head, 16);
    LRUOutput(head);
    printf("-----------------test case 9: move the existent data to the header ------------------\n");
    head = LRUAlogrithm(head, 4);
    LRUOutput(head); 
    LRUDestroy(head);
}

基于数组实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值