03、|链表:实现LRU(Least Recently Used)缓存淘汰算法
3.1 链表结构及其操作
与数组的对比:
不同的链表类型:
和单链表相比,循环链表的优点是从链尾到链头比较方便。当要处理的数据具有环型结构特点时,就特别适合采用循环链表。比如著名的约瑟夫问题——eg:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行直到仅余15个人为止。问怎样排法,才能使每次投入大海的都是非教徒。
3.2 轻松正确的写出链表代码
一、理解指针或引用的含义
含义——存储所指对象的内存地址
将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。
二、警惕指针丢失和内存泄漏
错误示范:插入节点
p -> next = x;
x -> next =p -> next;
自己指向了自己,正确顺序调换一二行即可。
如果是C语言之类的,删除链表结点是,记得手动释放内存空间
三、利用哨兵简化实现难度
在结点p之后插入一个新的结点:
new_node -> next =p ->next;
p -> next = new_node;
但是,向一个空链表插时,就需要特数处理
if(head == null){
head = new_node;
}
删除结点p的后继结点
p -> next = p ->next ->next;
但是,如果删除最后一个结点,上面的代码就不work了
if(head -> next == null){
head == null;
}
这个时候,我们引入哨兵结点,在任何时候,不管链表是不是空,head指针都会一直指向这个哨兵结点——这种链表也叫做带头链表。
eg:
代码一:
// 在数组a中,查找key,返回key所在的位置
// 其中,n表示数组a的长度
int find(char* a, int n, char key) {
// 边界条件处理,如果a为空,或者n<=0,说明数组中没有数据,就不用while循环比较了
if(a == null || n <= 0) {
return -1;
}
int i = 0;
// 这里有两个比较操作:i<n和a[i]==key.
while (i < n) {
if (a[i] == key) {
return i;
}
++i;
}
return -1;
}
代码二: