线性表的链式存储结构--链表(Linked)
链表(Linked)是用一组任意的存储单元存储线性表的数据元素,他们通过指针将这组零散的存储单元串联在一起。把存储单元(内存块)称为链表的结点,记录相邻结点的地址叫指针。
链表在进行插入或删除一个数据时是非常快速的,我们只需要考虑相邻结点的指针改变即可,所以对应的时间复杂度是O(1);但是因为数据并非连续存储的,所以无法像数组那样,根据首地址和下标,通过寻址公式直接计算出对应的内存地址,而是需要根据指针一个一个依次遍历,直到找到相应的节点,所以链表随机访问的时间复杂度是O(n)。
单链表
在每个链表的结点上除了存储数据之外,还需要记录链上下一个结点的地址,叫作后继指针。并习惯性的把第一个结点叫作头结点,把最后一个结点叫作尾结点。其中头结点用来记录链表的基地址,尾结点的指针指向一个空地址NULL
循环链表
循环链表是一种特殊的单链表。实际上循环链表是在单链表的基础上,单链表的尾结点指针指向链表的头结点,首尾相连形成一个环状。
和单链表相比,循环链表的优点是从链尾到链头比较方便。当要处理的数据具有环型结构特点时,就特别适合采用循环链表。
双向链表
双向链表支持两个方向,每个结点不止有一个后继指针next指向后面的结点,还有一个前驱指针prev指向前面的结点。
所以双向链表需要额外的两个空间来存储后继结点和前驱结点的地址,故双向链表需要比单链表占用更多的内存空间。但双向链表可以支持O(1)时间复杂度的情况下找到前驱结点,使得双向链表在某些情况下的插入、删除等操作都要比单向链表简单、高效,是空间换时间的一种设计思想。
双向循环链表
就是双向链表与循环链表的结合体,链表的头结点的prev指针指向链表的尾结点,链表的尾结点的next指针指向链表的头结点
静态链表
通过数组描述的链表叫做静态链表,又叫做游标实现法。首先让数组的元素都是由两个数据域组成,数据(data)和游标(cur),cur相当于单链表中的next指针,存放钙元素的后继元素在数组中的下表。
基于链表如何实现LRU缓存淘汰算法?
维护一个有序单链表,越靠近链表尾部的结点是越早之前访问的。当有一个新的数据被访问时,我们从链表头开始顺序遍历链表:
1.如果此数据之前已经被缓存在链表中了,我们遍历得到的这个数据对应的结点,并将其从原来的位置删除,然后再插入到链表的头部。
2.如果此数据没有在缓存链表中,又分为两种情况:
-如果此时缓存未满,则将此结点插入到链表的头部;
-如果此时缓存已满,则链表尾部结点删除,将新的数据结点插入链表的头部
实现一个链表技巧
1.理解指针或引用的含义
将某个变量赋值给指针,实际上就是讲这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。
2.警惕指针丢失和内存泄漏
在插入结点时,一定要注意操作的顺序。删除链表结点时,也一定要记得手动释放内存空间。
3.利用哨兵简化实现难度
针对链表的插入、删除操作,需要对插入第一个结点和删除最后一个结点的情况进行特殊处理。所以可以将链表加入哨兵结点,这样head指针都会一直指向这个哨兵结点。这样在删除或插入结点,都统一为相同的代码实现逻辑。
4.重点留意便捷条件处理
-如果链表为空时,代码是否能正常工作?
-如果链表只包含一个结点时,代码是否能否正常工作?
-如果链表只包含一个结点时,代码是否能正常工作?
-代码逻辑在处理头结点和尾结点的时候,是否能正常工作?