• 数据结构与算法4-链表


    目录

    链表的定义

    链表的特点

    单链表

    循环链表

    双向链表

    双向循环链表

    代码实现链表

    面试题-LRU缓存淘汰算法


    在上面数组的数据结果过了之后,接下来轮到链表

    无论对于哪种语言,链表都是一个很重要的数据结构

    链表的定义

    链表通过指针将一组零散的内存块组合在一起,其中,我们把内存块成为链表的“节点”,为了将所有节点串起来,每个链表的节点除了存储数据之外,还需要记录链表的下一个节点地址

    链表的结构如图所示

     data+next就是一个节点

    链表的特点

    1、不需要连续的内存空间

    2、有指针引用

    常见的链表结构:单向链表、双向链表、循环链表

    也正是因为链表的特点,所以链表没有所谓的内存地址计算公式,并且,因为链表每个节点除了数据之外还包含下一个节点的特性,存储相同的数据,链表占用的内存会比数组多

    单链表

    上面的链表结构示意图其实就是一个单链表

    链表不像数组那样有内存地址计算公式,所以如果想要在链表里进行查询,那就是从头开始,一个一个看next,直到找到对应的数据为止,也就是说,时间复杂度是O(n)

    链表的插入如何插入呢?插入的地方可以有三个,一个是从头插入,一个是从尾插入,一个是从中间插入,插入头或者尾的时候,我们只需要找到对应的位置,将next指向进行改变就可以了,而插入中间位置不大一样,需要改变插入节点前后的next指针

    它和数组的插入明显的不一样就是链表不用移动,链表的插入就是先断开指针,断开前指向当前,当前的next指向断开后,而数组如果插入尾部,其实和链表的效果是一样的,不过链表不需要考虑越界问题,但,需要考虑内存问题

    链表的删除,就更简单,直接把断开前的next指向断开后就好了

    链表还有两个特殊的节点,头节点和尾节点,头节点用来记录链表的基地址,拿到头节点,我们就可以遍历整个链表,而尾节点特殊的地方是因为,尾节点的next是NULL,表示这是链表的最后一个节点

    循环链表

    循环链表是一种特殊的单链表,它和单链表唯一的区别就在于尾节点,单链表的尾节点是指向一个空地址NULL,而循环链表的尾节点,指向的是头节点,就像一个环一样, 首尾相连,所以才叫做循环链表

    双向链表

    所谓双向链表,和单链表不同的是,单链表只有一个指向后置节点的next指针,而双向链表除了存在指向后置节点的指针之外,还存在一个指向前置节点的prev节点,也就是说,通过一个节点,获取完成这一整个链表

    当然,多了一个前置指针,占用的内存空间更大了

    双向循环链表

    双向链表存在一个延伸,那就是双向循环链表,和循环链表相似,也是尾节点的next指向头节点,当然,你可能会说,既然是双向链表,我尾节点一直prev也能获取到头,但是,有没有一种可能,直接尾节点.next更快呢?

    代码实现链表

    用代码实现链表,其实无非就是节点和每个节点之间的对应关系

    我们来构建一个这样的数据结构

    1. public class MyListNodes {
    2. private ListNode head;
    3. private int size = 0;
    4. public class ListNode {
    5. int value;
    6. ListNode next;
    7. public ListNode(int value) {
    8. this.value = value;
    9. this.next = null;
    10. }
    11. }
    12. }

    简单来写其实就是这样的,MyListNodes是我们的链表,ListNode是我们一个个的节点,根据我们上面说的特性,链表拿到一个head,头结点,就可以拿到整个链表,并且可以存储一个链表的长度方便后续的使用

    当然,链表是有它自己的一些方法的,我们实现一些经典的常用的方法,比如插入头、插入第n个、删除头、删除第n个、打印,当然,细节之处我们暂时忽略,比如越界问题,某个节点为空的异常等

    1. public class MyListNodes {
    2. private ListNode head;
    3. private int size = 0;
    4. /**
    5. * @Description: 插入头节点
    6. * @Author: create by YanXi on 2022/8/26 14:35
    7. * #Enail: best.you@icloud.com
    8. *
    9. * @param value 要插入的值
    10. * @exception
    11. * @Return: void
    12. */
    13. public void insertHead(int value) {
    14. ListNode node = new ListNode(value);
    15. // 不能直接替换,head可能有数据
    16. node.next = head;
    17. head = node;
    18. }
    19. /**
    20. * @Description: 插入某一个位置
    21. * @Author: create by YanXi on 2022/8/26 15:10
    22. * #Enail: best.you@icloud.com
    23. *
    24. * @param value 要插入的值
    25. * @param position 要插入的位置
    26. * @exception
    27. * @Return: void
    28. */
    29. public void insertNP(int value, int position) {
    30. // 如果等于 0 ,说明是头结点
    31. if (position == 0) {
    32. insertHead(value);
    33. } else {
    34. // 找到要插入的节点的位置
    35. ListNode nowPostition = head;
    36. for (int i = 0; i < position; i++) {
    37. nowPostition = nowPostition.next;
    38. }
    39. // 打断链表,插入新节点,并链接断开的点位
    40. ListNode node = new ListNode(value);
    41. // 断开后,让新的节点的指针指向原来的下一个节点
    42. node.next = nowPostition.next;
    43. // 将上一个节点的下一个节点指向新节点
    44. nowPostition.next = node;
    45. }
    46. }
    47. /**
    48. * @Description: 删除头节点
    49. * @Author: create by YanXi on 2022/8/26 15:12
    50. * @Email: best.you@icloud.com
    51. *
    52. * @param
    53. * @exception
    54. * @Return: void
    55. */
    56. public void deleteHead() {
    57. head = head.next;
    58. }
    59. /**
    60. * @Description: 删除某个位置的节点
    61. * @Author: create by YanXi on 2022/8/26 15:12
    62. * @Email: best.you@icloud.com
    63. *
    64. * @param position 要删除的位置
    65. * @exception
    66. * @Return: void
    67. */
    68. public void deleteNP(int position) {
    69. if (position == 0) {
    70. deleteHead();
    71. }else {
    72. ListNode nowPostition = head;
    73. for (int i = 0; i < position; i++) {
    74. nowPostition = nowPostition.next;
    75. }
    76. nowPostition.next = nowPostition.next.next;
    77. }
    78. }
    79. /**
    80. * @Description: 打印整个链表
    81. * @Author: create by YanXi on 2022/8/26 15:12
    82. * @Email: best.you@icloud.com
    83. *
    84. * @param
    85. * @exception
    86. * @Return: void
    87. */
    88. public void printList() {
    89. ListNode nowPostition = head;
    90. System.out.println(nowPostition.value);
    91. while (nowPostition.next != null) {
    92. System.out.println(nowPostition.value);
    93. nowPostition = nowPostition.next;
    94. }
    95. }
    96. /**
    97. * @Description: 查找指定值的node
    98. * @Author: create by YanXi on 2022/8/26 15:24
    99. * @Email: best.you@icloud.com
    100. *
    101. * @param queryValue 要查找的值
    102. * @exception
    103. * @Return: ListNode
    104. */
    105. public ListNode query(int queryValue) {
    106. ListNode nowPostition = head;
    107. while (nowPostition.next != null) {
    108. if (nowPostition.value == queryValue) {
    109. break;
    110. }
    111. nowPostition = nowPostition.next;
    112. }
    113. return nowPostition;
    114. }
    115. public class ListNode {
    116. int value;
    117. ListNode next;
    118. public ListNode(int value) {
    119. this.value = value;
    120. this.next = null;
    121. }
    122. }
    123. }

    当然, 我们也可以实现一个双向链表,双向链表比单链表多了一个前驱结点

    我们实现一下插入头方法就行了,其他的方法其实也就是类似的

    1. public class DoubleListNodes {
    2. private DoubleNode head; // 头结点
    3. private DoubleNode tail; // 尾结点
    4. public DoubleListNodes() {
    5. head = null;
    6. tail = null;
    7. }
    8. /**
    9. * @Description: 插入头结点
    10. * @Author: create by YanXi on 2022/8/28 16:10
    11. * @Email: best.you@icloud.com
    12. *
    13. * @param data 要插入的数据
    14. * @exception
    15. * @Return: void
    16. */
    17. public void insertHead(int data) {
    18. DoubleNode node = new DoubleNode(data);
    19. if (head == null) {
    20. tail = node;
    21. } else {
    22. head.pre = node;
    23. node.next = head;
    24. }
    25. head = node;
    26. }
    27. class DoubleNode{
    28. int value; // 值
    29. DoubleNode next; // 下一节点
    30. DoubleNode pre; // 前一节点
    31. public DoubleNode(int value) {
    32. this.value = value;
    33. this.next = null;
    34. this.pre = null;
    35. }
    36. }
    37. }

    面试题-LRU缓存淘汰算法

    如何设计LRU缓存淘汰算法?

    所谓LRU,就是长时间不使用的就淘汰掉

    我们只需要维护一个有序的单链表就行了,这个顺序,就是加入的时间排序


    如果新插入一个值,先遍历链表,如果存在,那就删除,并且插入到头结点,如果不存在,那就直接插入头部,当然,需要判断链表能否继续插入,毕竟LRU也是有存储限制的,如果有,直接插入,没有的话,那就删除最后的节点,最后的节点一定是相对于最少使用的

    当然,这只是一个简单的使用,实际在查询时,可以结合数组等方法,进行优化

  • 相关阅读:
    如何在 Spring Boot 中进行文件上传
    ts重点学习102-自动类型推论笔记
    c 语言基础:L1-038 新世界
    通过函数来提交表单数据,并且携带参数
    Linux项目自动化构建工具make/makefile
    ORB-SLAM2 ---- Initializer::ReconstructF函数
    51.Sentinel微服务保护
    华为S9312CPU占用率过高,查看BUFM占用最多
    从工程师到系统架构设计师【成长之路】
    开发板uboot与virtualbox虚拟机、windows11网络互通
  • 原文地址:https://blog.csdn.net/weixin_46097842/article/details/126249120