数据结构 —— java 单链表、双端链表、双向链表、无序链表、有序链表

桃扇骨 2023-06-27 06:15 142阅读 0赞

0、节点

结点 是数据结构中的基础,是 构成复杂数据结构的基本组成单位。

  1. public class Node {
  2. public long data;
  3. public Node next;
  4. public Node(long value) {
  5. this.data = value;
  6. }
  7. }

1、链表

链表:

通常由一连串 节点 组成,每个节点包含任意的实例数据(data fields)和一或两个用来指向上一个/或下一个节点的位置的链接(“links”)。

在这里插入图片描述

上面是一个单链表的存储原理图,head为头节点,它不存放任何的数据,只是充当一个指向链表中真正存放数据的第一个节点的作用,而每个节点中都有一个 next 引用,指向下一个节点,就这样一节一节往下面记录,直到最后一个节点,其中的 next 指向 null 。

2、链表的种类和特点

普通链表(单链表):
节点类保留下一节点的引用。链表类只保留头节点的引用,只能从头节点插入删除;

双端链表:
节点类保留下一节点的引用。链表类保留头节点、尾节点的引用,可以从尾节点插入,但只能从头节点删除;

双向链表:
节点类保留上一节点、下一节点的引用。链表类保留头节点、尾节点的引用,可以从尾节点插入删除;

如图所示:

在这里插入图片描述

以上统称为 无序链表

无序链表 最大特点就是 在头部或尾部增加 新节点。

有序链表:

递增,递减或者其他满足一定条件的规则,插入数据时,从头结点开始遍历每个节点,在满足规则的地方插入新节点。

3、普通链表、单链表(单端链表)

单链表 是链表中结构最简单的。一个单链表的节点(Node)分为两个部分,第一个部分(data)保存或者显示关于节点的信息,另一个部分存储下一个节点的地址。最后一个节点存储地址的部分指向空值。

  • 查找:单向链表 只可向一个方向遍历,一般查找一个节点的时候需要从第一个节点开始每次访问下一个节点,一直访问到需要的位置。
  • 插入:而插入一个节点,对于单向链表,我们只提供在链表头插入,只需要将当前插入的节点设置为头节点,next指向原头节点即可。
  • 删除:删除一个节点,我们将该节点的上一个节点的next指向该节点的下一个节点。

查找图:

在这里插入图片描述

在表头增加节点:

在这里插入图片描述

删除节点:

在这里插入图片描述

  1. package test.node1;
  2. public class Node {
  3. public long data;
  4. public Node next;
  5. public Node(long value) {
  6. this.data = value;
  7. }
  8. //显示方法
  9. public void display() {
  10. System.out.print(data + " ");
  11. }
  12. }
  13. package test.node1;
  14. /**
  15. * 单链表
  16. */
  17. public class SingleEndLinkList {
  18. /**头点节*/
  19. private Node first;
  20. public SingleEndLinkList() {
  21. first = null;
  22. }
  23. /**
  24. * 插入节点
  25. * <pre>
  26. * 在头结点之后插入
  27. * </pre>
  28. */
  29. public void insertFirst(long value) {
  30. Node aNode = new Node(value);
  31. aNode.next = first;
  32. first = aNode;
  33. }
  34. //删除头节点
  35. public Node deleteFirst() {
  36. Node tmp = first.next;
  37. first = tmp;
  38. return tmp;
  39. }
  40. //显示方法
  41. public void display() {
  42. Node now = first;
  43. while(now != null) {
  44. now.display();
  45. now = now.next;
  46. }
  47. System.out.println();
  48. }
  49. //查找方法
  50. public Node find(long value) {
  51. Node now = first;
  52. while(now.data != value) {
  53. if(now.next == null) {
  54. return null;
  55. }
  56. now = now.next;
  57. }
  58. return now;
  59. }
  60. //根据数值删除
  61. public Node delete(long value) {
  62. Node now = first;
  63. Node before = first;
  64. while(now.data != value) {
  65. if(now.next == null) {
  66. return null;
  67. }
  68. before = now;
  69. now = now.next;
  70. }
  71. if(now == first) {
  72. first = first.next;
  73. }else {
  74. before.next = now.next;
  75. }
  76. return now;
  77. }
  78. }

4、双端链表

节点类保留下一节点的引用。链表类保留头节点、尾节点的引用,可以从尾节点插入,但只能从头节点删除。

  1. package test.node2;
  2. public class Node {
  3. public long data;
  4. public Node next;
  5. public Node(long data) {
  6. this.data = data;
  7. }
  8. // 显示方法
  9. public void display() {
  10. System.out.print(data + " ");
  11. }
  12. }
  13. package test.node2;
  14. /**
  15. * 双端链表
  16. */
  17. public class DoubleEndLinkedList {
  18. // 头节点
  19. private Node first;
  20. //尾节点
  21. private Node last;
  22. public DoubleEndLinkedList() {
  23. first = null;
  24. last = null;
  25. }
  26. //插入节点,在头结点之后插入
  27. public void insertFirst(long value) {
  28. Node aNode = new Node(value);
  29. if (isEmpty()) {
  30. last = aNode;
  31. }
  32. aNode.next = first;
  33. first = aNode;
  34. }
  35. //尾节点插入
  36. public void insertLast(long value) {
  37. Node aNode = new Node(value);
  38. if (isEmpty()) {
  39. first = aNode;
  40. }
  41. else {
  42. last.next = aNode;
  43. }
  44. last = aNode;
  45. }
  46. //删除头节点
  47. public Node deleteFirst() {
  48. Node tmp = first;
  49. if (first.next == null) {
  50. last = null;
  51. }
  52. first = tmp.next;
  53. return tmp;
  54. }
  55. //显示方法
  56. public void display() {
  57. Node now = first;
  58. while(now != null) {
  59. now.display();
  60. now = now.next;
  61. }
  62. System.out.println();
  63. }
  64. //查找方法
  65. public Node find(long value) {
  66. Node now = first;
  67. while(now.data != value) {
  68. if(now.next == null) {
  69. return null;
  70. }
  71. now = now.next;
  72. }
  73. return now;
  74. }
  75. //根据数值删除
  76. public Node delete(long value) {
  77. Node now = first;
  78. Node before = first;
  79. while(now.data != value) {
  80. if(now.next == null) {
  81. return null;
  82. }
  83. before = now;
  84. now = now.next;
  85. }
  86. if(now == first) {
  87. first = first.next;
  88. }
  89. else {
  90. before.next = now.next;
  91. }
  92. return now;
  93. }
  94. //判断是否为空
  95. public boolean isEmpty() {
  96. return first == null;
  97. }
  98. }

为什么不能删除尾节点? 删除尾节点时必须知道尾节点的上个节点,但是 双端链表 只能知道下个节点,不知道上个节点,故无法删除。

5、双向链表

节点类保留上一节点、下一节点的引用。链表类保留头节点、尾节点的引用,可以从尾节点插入删除。

在这里插入图片描述

  1. package test.node3;
  2. public class Node {
  3. // 数据域
  4. public long data;
  5. //指针域(保存下一个节点的引用)
  6. public Node next;
  7. //指针域(保存前一个节点的引用)
  8. public Node previous;
  9. public Node(long value) {
  10. this.data = value;
  11. }
  12. /**
  13. * 显示方法
  14. */
  15. public void display() {
  16. System.out.print(data + " ");
  17. }
  18. }
  19. package test.node3;
  20. /**
  21. * 双向链表
  22. */
  23. public class DoubleByLinkedList {
  24. // 头结点
  25. private Node first;
  26. //尾结点
  27. private Node last;
  28. public DoubleByLinkedList() {
  29. first = null;
  30. last = null;
  31. }
  32. /**
  33. * 插入一个节点,在头结点后进行插入
  34. *
  35. * @param value
  36. */
  37. public void insertFirst(long value) {
  38. Node node = new Node(value);
  39. if (isEmpty()) {
  40. last = node;
  41. } else {
  42. first.previous = node;
  43. }
  44. node.next = first;
  45. first = node;
  46. }
  47. public void insertLast(long value) {
  48. Node node = new Node(value);
  49. if (isEmpty()) {
  50. first = node;
  51. } else {
  52. last.next = node;
  53. node.previous = last;
  54. }
  55. last = node;
  56. }
  57. /**
  58. * 删除一个结点,在头结点后进行删除
  59. *
  60. * @return
  61. */
  62. public Node deleteFirst() {
  63. Node tmp = first;
  64. if (first.next == null) {
  65. last = null;
  66. } else {
  67. first.next.previous = null;
  68. }
  69. first = tmp.next;
  70. return tmp;
  71. }
  72. /**
  73. * 删除一个结点,从尾部进行删除
  74. *
  75. * @return
  76. */
  77. public Node deleteLast() {
  78. if (first.next == null) {
  79. first = null;
  80. } else {
  81. last.previous.next = null;
  82. }
  83. last = last.previous;
  84. return last;
  85. }
  86. /**
  87. * 显示方法
  88. */
  89. public void display() {
  90. Node current = first;
  91. while (current != null) {
  92. current.display();
  93. current = current.next;
  94. }
  95. }
  96. /**
  97. * 查找方法
  98. *
  99. * @param value
  100. * @return
  101. */
  102. public Node find(long value) {
  103. Node current = first;
  104. while (current.data != value) {
  105. if (current.next == null) {
  106. return null;
  107. }
  108. current = current.next;
  109. }
  110. return current;
  111. }
  112. public Node delete(long value) {
  113. Node current = first;
  114. while (current.data != value) {
  115. if (current.next == null) {
  116. return null;
  117. }
  118. current = current.next;
  119. }
  120. if (current == first) {
  121. first = first.next;
  122. } else {
  123. current.previous.next = current.next;
  124. }
  125. return current;
  126. }
  127. /**
  128. * 判断是否为空
  129. *
  130. * @return
  131. */
  132. public boolean isEmpty() {
  133. return first == null;
  134. }
  135. }

6、有序列表

前面的链表实现插入数据都是无序的,在有些应用中需要链表中的数据有序,这称为有序链表。

在有序链表中,数据是按照关键值有序排列的。一般在大多数需要使用有序数组的场合也可以使用有序链表。有序链表优于有序数组的地方是插入的速度(因为元素不需要移动),另外链表可以扩展到全部有效的使用内存,而数组只能局限于一个固定的大小中。

有序列表算法题:

将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:

  1. 输入:1->2->4, 1->3->4
  2. 输出:1->1->2->3->4->4

https://leetcode-cn.com/problems/merge-two-sorted-lists/

想法:递归

我们可以如下递归地定义在两个链表里的 merge 操作(忽略边界情况,比如空链表等):

  1. ![20200104170853154.png][]

也就是说,两个链表头部较小的一个与剩下元素的 merge 操作结果合并。

算法
我们直接将以上递归过程建模,首先考虑边界情况。
特殊的,如果 l1 或者 l2 一开始就是 null ,那么没有任何操作需要合并,所以我们只需要返回非空链表。否则,我们要判断 l1 和 l2 哪一个的头元素更小,然后递归地决定下一个添加到结果里的值。如果两个链表都是空的,那么过程终止,所以递归过程最终一定会终止。

  1. class Solution {
  2. public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
  3. if (l1 == null) {
  4. return l2;
  5. }
  6. else if (l2 == null) {
  7. return l1;
  8. }
  9. else if (l1.val < l2.val) {
  10. l1.next = mergeTwoLists(l1.next, l2);
  11. return l1;
  12. }
  13. else {
  14. l2.next = mergeTwoLists(l1, l2.next);
  15. return l2;
  16. }
  17. }
  18. }

复杂度分析

  • 时间复杂度:O(n + m) 因为每次调用递归都会去掉 l1 或者 l2 的头元素(直到至少有一个链表为空),函数 mergeTwoList 中只会遍历每个元素一次。所以,时间复杂度与合并后的链表长度为线性关系。
  • 空间复杂度:O(n + m)。调用 mergeTwoLists 退出时 l1 和 l2 中每个元素都一定已经被遍历过了,所以 n + m个栈帧会消耗 O(n + m) 的空间。

发表评论

表情:
评论列表 (有 0 条评论,142人围观)

还没有评论,来说两句吧...

相关阅读