当前位置:   article > 正文

带你深入探索Java中的LinkedList和链表的奥秘

带你深入探索Java中的LinkedList和链表的奥秘

目录

引言

链表的概念

链表的结构

单向/双向

单向链表 (Singly Linked List)

双向链表 (Doubly Linked List)

带头/不带头

带头链表(用单向链表来演示)

​编辑

不带头链表(用单向链表来演示)

循环/非循环

循环链表(用单向链表来演示)

非循环链表(用单向链表来演示)

以上情况组合起来共有8种链表结构,不过大家不用担心,我们只需重点掌握两种:

链表的实现

无头单向链表的实现(未使用泛型)

1.跟顺序表一样,我们先创建一个接口类IList

2.创建MySingleList类并让它实现IList类中的方法

LinkedList 的模拟实现(未使用泛型)

创建 MyLinkedList 类并让它实现 IList 类中的方法

LinkedList 是什么

LinkedList的使用

LinkedList的构造

LinkedList 的其他常用方法介绍

 LinkedList的遍历

ArrayList和LinkedList的区别

总结


引言

        在 Java 编程语言中,链表是一种基本的数据结构,它提供了一种灵活的方式来存储和管理数据。Java 中的 LinkedList 类是链表实现的一个典型例子,它是 Java 集合框架的一部分。在这篇博客中,我们将深入探讨 LinkedList 和链表的概念、特点以及如何在 Java 中使用它们。

链表的概念

        链表是一种线性数据结构,由一系列节点(Node)组成,每个节点包含两个主要元素:数据和下一个节点的地址。链表的节点不必在内存中连续存储,它们可以分散存放,并通过存储在节点内的下一个节点的地址连接起来形成一个序列。

链表的结构

请注意:

  • 链式结构在逻辑上是连续的,但在物理空间上是不一点连续。
  • 节点的数据存储在堆区而不是栈区,这一点与顺序表相同(数组存储在栈区)。
  • 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。

单向/双向

单向链表 (Singly Linked List)

单向链表的每个节点包含:

  • val 域:存储数据元素。
  • next 域:存储下一个节点的地址。

双向链表 (Doubly Linked List)

        双向链表在单向链表的基础上增加了一个指向前一个节点的指针。

因此每个节点包含:

  • val 域:存储数据元素。
  • next 域:存储下一个节点的地址。
  • prev 域:存储上一个节点的地址。

带头/不带头

带头链表(用单向链表来演示)

        就是多了个节点用来记录链表头节点的地址。

不带头链表(用单向链表来演示)

         跟前面的单向链表一样,因为就是无头单链表

循环/非循环

循环链表(用单向链表来演示)

         就是把链表的最后一个节点用来存头节点的地址。

非循环链表(用单向链表来演示)

         跟前面的单向链表和无头链表一样,因为就是无头单向链表。

以上情况组合起来共有8种链表结构,不过大家不用担心,我们只需重点掌握两种:

  • 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。
  • 无头双向链表:在 Java 的集合框架库中 LinkedList 底层实现就是无头双向循环链表。

        链表的知识是一通百通的,其他情况大家不用担心,学完自然就懂了。

链表的实现

无头单向链表的实现(未使用泛型)

1.跟顺序表一样,我们先创建一个接口类 IList

  1. public interface IList {
  2. //头插法
  3. void addFirst(int data);
  4. //尾插法
  5. void addLast(int data);
  6. //任意位置插入,第一个数据节点为0号下标
  7. void addIndex(int index,int data);
  8. //查找是否包含关键字key是否在单链表当中
  9. boolean contains(int key);
  10. //删除第一次出现关键字为key的节点
  11. void remove(int key);
  12. //删除所有值为key的节点
  13. void removeAllKey(int key);
  14. //得到单链表的长度
  15. int size();
  16. void clear();
  17. void display();
  18. }

         这个接口类也可以不创建,但为了避免漏写功能还是建议创建。

2.创建 MySingleList 类并让它实现IList类中的方法

  1. public class MySingleList implements IList {
  2. static class ListNode {
  3. public int val;
  4. public ListNode next;
  5. public ListNode(int val) {
  6. this.val = val;
  7. }
  8. }
  9. public ListNode head;//用来保存头节点地址
  10. // public void createList() {
  11. // ListNode listNode1 = new ListNode(1);
  12. // ListNode listNode2 = new ListNode(2);
  13. // ListNode listNode3 = new ListNode(3);
  14. // ListNode listNode4 = new ListNode(4);
  15. // ListNode listNode5 = new ListNode(5);
  16. //
  17. // listNode1.next = listNode2;
  18. // listNode2.next = listNode3;
  19. // listNode3.next = listNode4;
  20. // listNode4.next = listNode5;
  21. //
  22. // this.head = listNode1;
  23. // }
  24. //头插法
  25. @Override
  26. public void addFirst(int data) {
  27. ListNode listNode = new ListNode(data);
  28. // if (this.head == null) {
  29. // this.head = listNode;
  30. // return;
  31. // }
  32. listNode.next = this.head;
  33. this.head = listNode;
  34. }
  35. //尾插法
  36. @Override
  37. public void addLast(int data) {
  38. ListNode listNode = new ListNode(data);
  39. ListNode tmp = this.head;
  40. if (tmp == null) {
  41. this.head = listNode;
  42. } else {
  43. while (tmp.next != null) {
  44. tmp = tmp.next;
  45. }
  46. tmp.next = listNode;
  47. }
  48. }
  49. //任意位置插入,第一个数据节点为0号下标
  50. @Override
  51. public void addIndex(int index, int data) {
  52. if (index < 0 || index > size()) {
  53. throw new IndexIllegality("插入元素位置异常:" + index);
  54. }
  55. if (index == 0) {
  56. addFirst(data);
  57. return;
  58. }
  59. if (index == size()) {
  60. addLast(data);
  61. return;
  62. }
  63. ListNode tmp = searchPrev(index);
  64. ListNode listNode = new ListNode(data);
  65. listNode.next = tmp.next;
  66. tmp.next = listNode;
  67. }
  68. //查找是否包含关键字key是否在单链表当中
  69. private ListNode searchPrev(int index) {
  70. ListNode tmp = this.head;
  71. while (index - 1 != 0) {
  72. tmp = tmp.next;
  73. --index;
  74. }
  75. return tmp;
  76. }
  77. //查找是否包含关键字key是否在单链表当中
  78. @Override
  79. public boolean contains(int key) {
  80. ListNode tmp = this.head;
  81. while (tmp != null) {
  82. if (tmp.val == key) {
  83. return true;
  84. }
  85. tmp = tmp.next;
  86. }
  87. return false;
  88. }
  89. //删除第一次出现关键字为key的节点
  90. @Override
  91. public void remove(int key) {
  92. // if (this.head == null) {
  93. // System.out.println("当前链表无数据,无法进行删除!");
  94. // return;
  95. // }
  96. if (isEmpty()) {
  97. return;
  98. }
  99. if (this.head.val == key) {
  100. this.head = this.head.next;
  101. return;
  102. }
  103. ListNode tmp = findPrev(key);
  104. if (tmp == null) {
  105. System.out.println("要删除的数不存在!");
  106. return;
  107. }
  108. ListNode del = tmp.next;
  109. tmp.next = del.next;
  110. }
  111. //判断链表是否为空,仅做内部支持
  112. private boolean isEmpty() {
  113. if (this.head == null) {
  114. System.out.println("当前链表无数据,无法进行删除!");
  115. return true;
  116. }
  117. return false;
  118. }
  119. //根据key来查找前驱节点的地址,仅做内部支持
  120. private ListNode findPrev(int key) {
  121. ListNode tmp = this.head;
  122. while (tmp.next != null) {
  123. if (tmp.next.val == key) {
  124. return tmp;
  125. }
  126. tmp = tmp.next;
  127. }
  128. return null;
  129. }
  130. //删除所有值为key的节点
  131. @Override
  132. public void removeAllKey(int key) {
  133. if (isEmpty()) {
  134. return;
  135. }
  136. ListNode prev = this.head;
  137. ListNode tmp = this.head.next;
  138. while (tmp != null) {
  139. if (tmp.val == key) {
  140. prev.next = tmp.next;
  141. } else {
  142. prev = tmp;
  143. }
  144. tmp = tmp.next;
  145. }
  146. if (this.head.val == key) {
  147. this.head = this.head.next;
  148. }
  149. }
  150. //获得单链表的长度
  151. @Override
  152. public int size() {
  153. int count = 0;
  154. ListNode tmp = this.head;
  155. while (tmp != null) {
  156. ++count;
  157. tmp = tmp.next;
  158. }
  159. return count;
  160. }
  161. //销毁链表
  162. @Override
  163. public void clear() {
  164. ListNode tmp = this.head;
  165. this.head = null;
  166. while (tmp != null) {
  167. ListNode cur = tmp.next;
  168. tmp.next = null;
  169. tmp = cur;
  170. }
  171. }
  172. //打印全部链表,仅做测试
  173. @Override
  174. public void display() {
  175. ListNode tmp = this.head;
  176. while (tmp != null) {
  177. System.out.print(tmp.val + " ");
  178. tmp = tmp.next;
  179. }
  180. System.out.println();
  181. }
  182. //从指定的节点开始打印链表,仅做测试
  183. public void display(ListNode newHead) {
  184. ListNode tmp = newHead;
  185. while (tmp != null) {
  186. System.out.print(tmp.val + " ");
  187. tmp = tmp.next;
  188. }
  189. System.out.println();
  190. }
  191. //逆置链表,仅做测试
  192. public ListNode reverseList() {
  193. //为空
  194. if (this.head == null) {
  195. return null;
  196. }
  197. //只有一个节点
  198. if (this.head.next == null) {
  199. return this.head;
  200. }
  201. ListNode tmp = this.head.next;
  202. this.head.next = null;
  203. while (tmp != null) {
  204. ListNode tmpNext = tmp.next;
  205. tmp.next = this.head;
  206. this.head = tmp;
  207. tmp =tmpNext;
  208. }
  209. return this.head;
  210. }
  211. }

         抛异常部分请自行完成,或用if语句代替。

LinkedList 的模拟实现(未使用泛型)

创建 MyLinkedList 类并让它实现 IList 类中的方法

        MyLinkedList 同样接入 IList 接口就行,不用再写一个接口类。LinkedList 实际上是无头双向链表,因此我们实现这个无头双向链表即可。

  1. public class MyLinkedList implements IList {
  2. static class ListNode {
  3. public int val;
  4. public ListNode next;
  5. public ListNode prev;
  6. public ListNode(int val) {
  7. this.val = val;
  8. }
  9. }
  10. public ListNode head;//用来保存头节点地址
  11. public ListNode last;//用来保存尾节点地址
  12. //头插法
  13. @Override
  14. public void addFirst(int data) {
  15. ListNode tmp = new ListNode(data);
  16. if (this.head == null) {
  17. this.head = tmp;
  18. this.last = tmp;
  19. } else {
  20. tmp.next = this.head;
  21. this.head.prev = tmp;
  22. this.head = tmp;
  23. }
  24. }
  25. //尾插法
  26. @Override
  27. public void addLast(int data) {
  28. ListNode tmp = new ListNode(data);
  29. if (this.last == null) {
  30. this.head = tmp;
  31. this.last = tmp;
  32. } else {
  33. last.next = tmp;
  34. tmp.prev = this.last;
  35. this.last = tmp;
  36. }
  37. }
  38. //任意位置插入,第一个数据节点为0号下标
  39. @Override
  40. public void addIndex(int index, int data) throws IndexIllegality {
  41. int len = size();
  42. if (index < 0 || index > len) {
  43. throw new IndexIllegality("插入元素位置异常:" + index);
  44. }
  45. if (index == 0) {
  46. addFirst(data);
  47. return;
  48. }
  49. if (index == len) {
  50. addLast(data);
  51. return;
  52. }
  53. ListNode tmp = findIndex(index);
  54. ListNode listNode = new ListNode(data);
  55. listNode.next = tmp;
  56. tmp.prev.next = listNode;
  57. listNode.prev = tmp.prev;
  58. tmp.prev = listNode;
  59. }
  60. //查找指定下标(实际上是没有下标的)的节点地址,仅做内部支持
  61. private ListNode findIndex(int index) {
  62. ListNode tmp = this.head;
  63. while (index != 0) {
  64. tmp = tmp.next;
  65. --index;
  66. }
  67. return tmp;
  68. }
  69. //查找是否包含关键字key是否在单链表当中
  70. @Override
  71. public boolean contains(int key) {
  72. ListNode tmp = this.head;
  73. while (tmp != null) {
  74. if (tmp.val == key) {
  75. return true;
  76. }
  77. tmp = tmp.next;
  78. }
  79. return false;
  80. }
  81. //删除第一次出现关键字为key的节点
  82. @Override
  83. public void remove(int key) {
  84. if (isEmpty()) {
  85. return;
  86. }
  87. if (this.head.val == key) {
  88. this.head = this.head.next;
  89. if (this.head != null) {
  90. this.head.prev = null;
  91. }
  92. return;
  93. }
  94. if (this.last.val == key) {
  95. this.last = this.last.prev;
  96. if (this.last != null) {
  97. this.last.next = null;
  98. }
  99. return;
  100. }
  101. ListNode tmp = findKey(key);
  102. if (tmp == null) {
  103. System.out.println("要删除的数不存在!");
  104. return;
  105. }
  106. tmp.prev.next = tmp.next;
  107. tmp.next.prev = tmp.prev;
  108. }
  109. //根据key查找节点地址,仅做内部支持
  110. private ListNode findKey(int key) {
  111. ListNode tmp = this.head;
  112. while (tmp != null) {
  113. if (tmp.val == key) {
  114. return tmp;
  115. }
  116. tmp = tmp.next;
  117. }
  118. return null;
  119. }
  120. //判断链表是否为空,仅做内部支持
  121. private boolean isEmpty() {
  122. if (this.head == null) {
  123. System.out.println("当前链表无数据,无法进行删除!");
  124. return true;
  125. }
  126. return false;
  127. }
  128. //删除所有值为key的节点
  129. @Override
  130. public void removeAllKey(int key) {
  131. ListNode tmp = this.head;
  132. while (tmp != null) {
  133. if (tmp.val == key) {
  134. if (tmp == this.head) {
  135. this.head = this.head.next;
  136. if (this.head == null) {
  137. this.last = null;
  138. } else {
  139. this.head.prev = null;
  140. }
  141. } else {
  142. tmp.prev.next = tmp.next;
  143. if (tmp.next == null) {
  144. this.last = this.last.prev;
  145. } else {
  146. tmp.next.prev = tmp.prev;
  147. }
  148. }
  149. }
  150. tmp = tmp.next;
  151. }
  152. }
  153. //获得单链表的长度
  154. @Override
  155. public int size() {
  156. ListNode tmp = this.head;
  157. int count = 0;
  158. while (tmp != null) {
  159. ++count;
  160. tmp = tmp.next;
  161. }
  162. return count;
  163. }
  164. //销毁链表
  165. @Override
  166. public void clear() {
  167. ListNode tmp = this.head;
  168. this.head = null;
  169. this.last = null;
  170. while (tmp != null) {
  171. ListNode cur = tmp.next;
  172. tmp.next = null;
  173. tmp.prev = null;
  174. tmp = cur;
  175. }
  176. }
  177. //打印全部链表,仅做测试
  178. @Override
  179. public void display() {
  180. ListNode tmp = this.head;
  181. while (tmp != null) {
  182. System.out.print(tmp.val + " ");
  183. tmp = tmp.next;
  184. }
  185. System.out.println();
  186. }
  187. }

         跟上面一样,抛异常部分请自行完成,或用if语句代替。

LinkedList 是什么

        LinkedList 的底层是双向链表结构,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。

在集合框架中,LinkedList 也实现了 List 接口,具体如下:

 请注意:

  • LinkedList 实现了 List 接口。
  • LinkedList 的底层使用了双向链表。
  • LinkedList 没有实现 RandomAccess 接口,因此 LinkedList 不支持随机访问。
  • LinkedList 的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)。
  • LinkedList 比较适合任意位置插入的场景。

LinkedList的使用

LinkedList的构造

方法解释
LinkedList()无参构造
public LinkedList(Collection<? extends E> c)使用其他集合容器中元素构造 List
  1. public static void main(String[] args) {
  2. List<Integer> list1 = new LinkedList<>();//通过接口new
  3. LinkedList<String> list2 = new LinkedList<>();//直接new
  4. List<String> list3 = new java.util.ArrayList<>();//使用其他集合容器中元素构造List
  5. list2.add("Java");
  6. //使用ArrayList构造LinkedList
  7. List<String> list4 = new LinkedList<>(list3);
  8. //使用LinkedList构造LinkedList
  9. List<String> list5 = new LinkedList<>(list2);
  10. }

         个人推荐使用第一种和第二种方法。

LinkedList 的其他常用方法介绍

方法解释
boolean add(E e)尾插 e
void add(int index, E element)将 e 插入到 index 位置
boolean addAll(Collection<? extends E> c)尾插 c 中的元素
E remove(int index)删除 index 位置元素
boolean remove(Object o)删除遇到的第一个 o
E get(int index)获取下标 index 位置元素
E set(int index, E element)将下标 index 位置元素设置为 element
void clear()清空
boolean contains(Object o)判断 o 是否在线性表中
int indexOf(Object o)返回第一个 o 所在下标
int lastIndexOf(Object o)返回最后一个 o 的下标
List<E> subList(int fromIndex, int toIndex)截取部分 list

 LinkedList 的遍历

代码演示如下:

  1. public static void main(String[] args) {
  2. List<Integer> list = new LinkedList<>();//通过List接口来new一个LinkedList
  3. list.add(1);
  4. list.add(2);
  5. list.add(3);
  6. //使用for循环
  7. for (int i = 0; i < list.size(); i++) {
  8. System.out.print(list.get(i) + " ");
  9. }
  10. System.out.println();
  11. //使用for-each(增强for循环)
  12. for (Integer integer : list) {
  13. System.out.print(integer + " ");
  14. }
  15. System.out.println();
  16. //使用迭代器
  17. Iterator<Integer> it = list.listIterator();//迭代器
  18. while (it.hasNext()) {
  19. System.out.print(it.next() + " ");
  20. }
  21. System.out.println();
  22. //使用反向迭代器---反向遍历
  23. ListIterator<Integer> rit = list.listIterator(list.size());
  24. while (rit.hasPrevious()){
  25. System.out.print(rit.previous() +" ");
  26. }
  27. }

运行结果如下:

ArrayList 和 LinkedList 的区别

不同点ArrayListLinkedList
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持O(1)不支持:O(N)
头插需要搬移元素,效率低O(N)只需修改引用的指向,时间复杂度为O(1)
插入空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁

总结

        LinkedList 是 Java 中实现链表概念的一个强大工具。它提供了一系列方法来高效地进行数据的插入、删除和访问操作。了解 LinkedList 的工作原理和如何在 Java 中使用它对于开发高效的程序至关重要。无论是在学术研究还是实际开发中,LinkedList  都是一个不可或缺的组件。

        以上,就是的本次要带大家深度剖析的Java中的LinkedList和链表的全部内容,感谢大家愿意花时间阅读本文!如有错误,建议,或问题均可在评论区指出!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/822982
推荐阅读
相关标签
  

闽ICP备14008679号