什么是链表?

链表是有序的数据结构,链表中的每个部分称为节点。可以首、尾、中间进行数据存取,链表的元素在内存中不必是连续的空间,每个节点通过 next 指针指向下一个节点。

image.png

优点

链表的添加和删除不会导致其余元素位移。

缺点

无法根据索引快速定位元素。

数组和链表区别

  • 获取、修改元素时,数组效率高
  • 添加、删除元素时,链表效率高
  • 数组添加、移除会导致后续元素位移,性能开销大

环形链表

指的是链表最后一个节点的 next 指向第一个节点,形成首尾相连的循环结构。

链表环路检测

解题思路:

  1. 快慢指针法实现
  2. slow 指针每次移动一位,fast 指针每次移动两位,如果他们相遇则说明链表存在环
  3. 新指针 ptrhead 开始移动,与 slow 相遇的位置即为环的起点
  4. fast 指针移动距离为 slow 的两倍可得出公式:a + (nb + nc + b) = 2(a + b)
  5. 变化处理后得到新公式:a = (n - 1)(b + c) + c

链表 - 图2

实现功能

JavaScript 中没有链表,但是可以通过 Ojbect 进行实现链表的所有功能。

节点类:

  • value 当前节点值
  • next 指向下一个节点

链表类:

  • addAtTail 尾部添加节点
  • addAtHead 头部添加节点
  • addAtIndex () 指定位置添加节点
  • get 获取节点
  • removeAtIndex () 删除指定节点

应用场景

leetcode 两数相加 。

案例

通过 Object 实现

  1. const a = { val: 'a' };
  2. const b = { val: 'b' };
  3. const c = { val: 'c' };
  4. const d = { val: 'd' };
  5. a.next = b;
  6. b.next = c;
  7. c.next = d;
  8. // 遍历链表
  9. let p = a;
  10. while (p) {
  11. console.log(p.val);
  12. p = p.next;
  13. }
  14. // 插入
  15. const e = { val: 'e' };
  16. c.next = e;
  17. e.next = d;
  18. // 删除
  19. c.next = d;

通过类模拟实现

  1. /**
  2. * 节点类
  3. */
  4. class Node {
  5. constructor(value) {
  6. this.value = value;
  7. this.next = null;
  8. }
  9. }
  10. /**
  11. * 链表类
  12. */
  13. class Link {
  14. constructor() {
  15. this.head = null;
  16. this.count = 0;
  17. }
  18. /**
  19. * 尾部添加节点
  20. */
  21. addAtTail(item) {
  22. if (this.count === 0) {
  23. this.head = new Node(item);
  24. this.count++;
  25. return this.head;
  26. }
  27. let endNode = this.head;
  28. while (endNode.next) {
  29. endNode = endNode.next;
  30. }
  31. endNode.next = new Node(item);
  32. this.count++;
  33. return item;
  34. }
  35. /**
  36. * 头部添加节点
  37. */
  38. addAtHead(item) {
  39. if (this.head) {
  40. const oldHead = this.head;
  41. this.head = new Node(item);
  42. this.head.next = oldHead;
  43. } else {
  44. this.head = new Node(item);
  45. }
  46. this.count++;
  47. return item;
  48. }
  49. /**
  50. * 指定位置添加节点
  51. */
  52. addAtIndex(index, item) {
  53. if (this.isInvalidIndex(index) || !this.head) {
  54. return -1;
  55. }
  56. if (index === 0) {
  57. this.addAtHead(item);
  58. return item;
  59. }
  60. let prev = this.head;
  61. while (index > 0) {
  62. index--;
  63. }
  64. const next = prev.next;
  65. const cur = new Node(item);
  66. prev.next = cur;
  67. cur.next = next;
  68. this.count++;
  69. return item;
  70. }
  71. /**
  72. * 获取节点
  73. */
  74. get(index) {
  75. if (this.isInvalidIndex(index) || !this.head) {
  76. return -1;
  77. }
  78. let cur = this.head;
  79. while (index > 0) {
  80. cur = cur.next;
  81. index--;
  82. }
  83. return cur;
  84. }
  85. /**
  86. * 删除指定节点
  87. */
  88. removeAtIndex(index) {
  89. if (this.isInvalidIndex(index) || !this.head) {
  90. return -1;
  91. }
  92. if (index === 0) {
  93. const oldHead = this.head;
  94. this.head = this.head.next;
  95. this.count--;
  96. return oldHead;
  97. }
  98. let prev = this.head;
  99. while (index - 1 > 0) {
  100. prev = prev.next;
  101. index--;
  102. }
  103. const cur = prev.next;
  104. prev.next = cur.next;
  105. this.count--;
  106. return cur;
  107. }
  108. /**
  109. * 判断index是否为空,是否超出数据长度大小
  110. */
  111. isInvalidIndex(index) {
  112. if (index === '' || index === null || index === undefined || index < 0 || index > this.count - 1) {
  113. return true;
  114. }
  115. return false;
  116. }
  117. }
  118. const link = new Link();
  119. link.addAtTail('a');
  120. link.addAtTail('b');
  121. link.addAtTail('c');