当前位置:   article > 正文

数据结构之LinkedList与链表(上)

数据结构之LinkedList与链表(上)

找往期文章包括但不限于本期文章中不懂的知识点:

个人主页:我要学编程(ಥ_ಥ)-CSDN博客

所属专栏:数据结构(Java版)

目录

手动实现单链表的源码

手动实现双链表的源码

分析 LinkedList 的源码 

LinkedList的使用 

常用方法 

ArrayList和LinkedList的区别


单链表的学习,点我

上面这篇博文是关于链表的基础知识、链表的分类以及单链表的实现。都详细的进行了说明。

手动实现单链表的源码

下面是Java版的链表源码: 

  1. // 无头单向非循环链表实现
  2. public class SingleLinkedList {
  3. // 链表是有一个一个的节点组成,因此得先创建节点
  4. // 链表的节点由两部分组成:数值域与地址域
  5. // 既然有多个部分,那么就可以写在类中
  6. // 又因为节点是在单链表中(单链表中包含一个又一个的节点)
  7. // 因此节点就可以成为单链表的内部类
  8. // 静态内部类可以只通过类名来访问,方便
  9. public static class listNode {
  10. public int val; // 存放数值
  11. // 是一个引用指向下一个节点的地址
  12. public listNode next; // 指向下一个节点的地址
  13. public listNode(int val) {
  14. // 新创建的节点的next应该为null,因为next为字段。因此其默认值为null,无需修改
  15. this.val = val;
  16. }
  17. }
  18. public listNode head; // 头节点
  19. // 头插法
  20. public void addFirst(int data){
  21. listNode newNode = new listNode(data);
  22. // 先判断这个链表是否有节点
  23. if (head == null) {
  24. //那么新增的这个节点就是头节点
  25. head = newNode;
  26. }else {
  27. // 把新节点的next指向head,并且把head指向新节点
  28. newNode.next = head;
  29. head = newNode;
  30. }
  31. }
  32. // 尾插法
  33. public void addLast(int data){
  34. listNode newNode = new listNode(data);
  35. // 先判断这个链表是否为空
  36. if (head == null) {
  37. head = newNode;
  38. }else {
  39. listNode cur = head;
  40. // 找到尾节点
  41. while (cur.next != null) {
  42. cur = cur.next;
  43. }
  44. // 将尾节点的next指向新节点,就完成了尾插
  45. cur.next = newNode;
  46. }
  47. }
  48. // 任意位置插入,第一个数据节点为0号下标
  49. public boolean addIndex(int index,int data) throws AddIndexofException{
  50. // 先判断插入的位置是否合法
  51. if (index < 0 || index > size()) {
  52. throw new AddIndexofException("addIndex() index位置异常");
  53. }
  54. // 先判断这个链表是否为空,如果是空,既可以采取尾插,也可以采取头插
  55. if (head == null) {
  56. addLast(data);
  57. }else if (index == 0) {
  58. addFirst(data);
  59. }else {
  60. listNode newNode = new listNode(data);
  61. // 找到pos-1的位置
  62. listNode cur = head;
  63. while (index-1 > 0) {
  64. cur = cur.next;
  65. index--;
  66. }
  67. // 两个的顺序不能变
  68. newNode.next = cur.next;
  69. cur.next = newNode;
  70. return true;
  71. }
  72. return false;
  73. }
  74. // 查找是否包含关键字key是否在单链表当中
  75. public boolean contains(int key){
  76. // 先判断链表是否为空
  77. if (head == null) {
  78. return false;
  79. }
  80. listNode cur = head;
  81. while (cur != null) {
  82. if (cur.val == key) {
  83. return true;
  84. }
  85. cur = cur.next;
  86. }
  87. return false;
  88. }
  89. // 删除第一次出现关键字为key的节点
  90. public void remove(int key) throws SingleLinkedisEmptyException, NotFindException{
  91. // 判断链表是否为空
  92. if (head == null) {
  93. throw new SingleLinkedisEmptyException("单链表为空异常");
  94. } else if (head.next == null) {
  95. // 如果链表中只有一个元素的话,直接删除就行
  96. if (head.val == key) {
  97. head = null;
  98. return;
  99. }
  100. else {
  101. throw new NotFindException("找不到要删除的数异常");
  102. }
  103. } else if (head.val == key) {
  104. // 头删
  105. head = head.next;
  106. return;
  107. } else {
  108. listNode cur = head;
  109. while (cur.next != null) {
  110. if (cur.next.val == key) {
  111. // 开始删除
  112. listNode del = cur.next.next;
  113. cur.next = cur.next.next;
  114. // 可能这里会有疑惑被删除的节点的next不是还指向了其后一个位置吗?
  115. // 这样会不会有问题呢?当一个对象每人引用时,就会被标记回收
  116. // 当然手动置为null,也是可以的
  117. del = null;
  118. return;
  119. }
  120. cur = cur.next;
  121. }
  122. }
  123. throw new NotFindException("找不到要删除的数异常");
  124. }
  125. // 删除所有值为key的节点
  126. public void removeAllKey(int key){
  127. boolean flag = true;
  128. // 判断链表是否为空
  129. if (head == null) {
  130. throw new SingleLinkedisEmptyException("单链表为空异常");
  131. } else if (head.next == null) {
  132. // 如果链表中只有一个元素的话,直接删除就行
  133. if (head.val == key) {
  134. head = null;
  135. return;
  136. }else {
  137. throw new NotFindException("没有找到要删除的数据!");
  138. }
  139. } else if (head.val == key) {
  140. // 如果头节点的值key,那就循环删除直至头节点的不为key
  141. while (head.val == key) {
  142. // 如果删除的只剩下最后一个节点了,就删除返回就行
  143. if (head.next == null) {
  144. head = head.next;
  145. return;
  146. }
  147. }
  148. flag = false;
  149. } else {
  150. listNode cur = head;
  151. while (cur.next != null) {
  152. if (cur.next.val == key) {
  153. // 开始删除
  154. listNode del = cur.next.next;
  155. cur.next = cur.next.next;
  156. // 可能这里会有疑惑被删除的节点的next不是还指向了其后一个位置吗?
  157. // 这样会不会有问题呢?当一个对象每人引用时,就会被标记回收
  158. // 当然手动置为null,也是可以的
  159. del = null;
  160. flag = false;
  161. }else {
  162. // 可能会出现连续的数字的情况
  163. // 因此删除之后,还得判断这个cur之后的元素是否为key
  164. cur = cur.next;
  165. }
  166. }
  167. }
  168. if (flag) {
  169. throw new NotFindException("找不到要删除的数异常");
  170. }
  171. }
  172. // 得到单链表的长度
  173. public int size(){
  174. // 先判断这个链表是否为空
  175. if (head == null) {
  176. return 0;
  177. }
  178. listNode cur = head;
  179. int count = 0;
  180. while (cur != null) {
  181. count++;
  182. cur = cur.next;
  183. }
  184. return count;
  185. }
  186. // 打印链表
  187. public void display(){
  188. listNode cur = head;
  189. while (cur != null) {
  190. System.out.print(cur.val+" ");
  191. cur = cur.next;
  192. }
  193. System.out.println();
  194. }
  195. // 清空链表
  196. public void clear(){
  197. // 暴力解法:把head置为null,那么就没有引用指向这个头节点了
  198. // 那么这个头节点就会被垃圾回收器给回收掉
  199. // head = null;
  200. // 上面那种方式可能会出现不安全的情况,因此采用遍历的形式
  201. listNode cur = head;
  202. while (cur != null) {
  203. // cur.val = null;
  204. listNode curNext = cur.next;
  205. cur.next = null;
  206. cur = curNext;
  207. }
  208. head = null;
  209. }
  210. }

AddlndexofException异常: 

  1. public class AddIndexofException extends RuntimeException{
  2. public AddIndexofException() {
  3. super();
  4. }
  5. public AddIndexofException(String msg) {
  6. super(msg);
  7. }
  8. }

 NotFindException异常:

  1. public class NotFindException extends RuntimeException{
  2. public NotFindException() {
  3. super();
  4. }
  5. public NotFindException(String msg) {
  6. super(msg);
  7. }
  8. }

 SingleLinkedisEmptyException异常:

  1. public class SingleLinkedisEmptyException extends RuntimeException{
  2. public SingleLinkedisEmptyException() {
  3. super();
  4. }
  5. public SingleLinkedisEmptyException(String msg) {
  6. super(msg);
  7. }
  8. }

 这里来说明一下:为什么节点的引用是节点类型?

首先,我们最开始在学习引用的时候,说过引用其实就是一个变量类型,用来存放地址。而在C语言中是用指针来存放地址,按照C语言的说法,节点的地址就得用节点类型的指针来接收。那么listNode 类型的 next,就得用 listNode 来接收(引用)。就好像,在 new 应该对象时,这个对象的地址会给到一个引用变量,而这个引用变量的类型就是 new 关键字后面的。例如:Person person = new Person();  。

双链表的学习,点我

上面这篇博文是关于链表的基础知识、链表的分类以及单链表的实现。都详细的进行了说明。

手动实现双链表的源码

  1. public class MyLinkedList implements LinkedList {
  2. //创建一个节点内部类
  3. static class ListNode {
  4. int val;
  5. ListNode prev;
  6. ListNode next;
  7. public ListNode(int val) {
  8. this.val = val;
  9. }
  10. }
  11. public ListNode head;
  12. public ListNode last;
  13. // 头插
  14. @Override
  15. public void addFirst(int data) {
  16. // 创建一个新的节点
  17. ListNode newNode = new ListNode(data);
  18. // 先判断链表是否为空
  19. if (head == null) {
  20. head = newNode;
  21. last = newNode;
  22. }else {
  23. newNode.next = head;
  24. head.prev = newNode;
  25. head = newNode;
  26. }
  27. }
  28. @Override
  29. public void addLast(int data) {
  30. // 创建一个新的节点
  31. ListNode newNode = new ListNode(data);
  32. // 先判断链表是否为空
  33. if (head == null) {
  34. head = newNode;
  35. last = newNode;
  36. }else {
  37. last.next = newNode;
  38. newNode.prev = last;
  39. last = newNode;
  40. }
  41. }
  42. @Override
  43. public void addIndex(int data, int index) throws addIndexOfException {
  44. // 判断这个index是否合法
  45. if (index < 0 || index > size()) {
  46. // 抛异常
  47. throw new addIndexOfException("下标位置不合法异常!");
  48. }
  49. if (index == 0) {
  50. // 头插
  51. addFirst(data);
  52. } else if (index == size()) {
  53. // 尾插
  54. addLast(data);
  55. } else {
  56. // 创建一个新的节点
  57. ListNode newNode = new ListNode(data);
  58. ListNode cur = head;
  59. // 找到要插入的前一个位置
  60. while (index-1 > 0) {
  61. cur = cur.next;
  62. index--;
  63. }
  64. ListNode curNext = cur.next;
  65. cur.next = newNode;
  66. newNode.next = curNext;
  67. newNode.prev = cur;
  68. curNext.prev = newNode;
  69. }
  70. }
  71. @Override
  72. public boolean contains(int key) {
  73. ListNode cur = head;
  74. while (cur != null) {
  75. if (cur.val == key) {
  76. return true;
  77. }
  78. cur = cur.next;
  79. }
  80. return false;
  81. }
  82. @Override
  83. public void remove(int key) {
  84. if (head == null) {
  85. // 链表为空,就抛异常
  86. throw new LinkedListIsEmptyException("链表为空异常!");
  87. }else if (head.next == null) {
  88. // 链表里面只有一个元素
  89. if (head.val == key) {
  90. head = null;
  91. last = null;
  92. return;
  93. }else {
  94. throw new NotFindException("找不到要删除的数异常!");
  95. }
  96. }else {
  97. // 头删
  98. if (head.val == key) {
  99. head = head.next;
  100. return;
  101. } else if (last.val == key) {
  102. // 尾删
  103. last = last.prev;
  104. last.next = null;
  105. return;
  106. }
  107. ListNode cur = head;
  108. while (cur != null) {
  109. if (cur.val == key) {
  110. // 开始删除
  111. ListNode curNext = cur.next;
  112. cur.prev.next = curNext;
  113. curNext.prev = cur.prev;
  114. return;
  115. }
  116. cur = cur.next;
  117. }
  118. throw new NotFindException("找不到要删除的数异常!");
  119. }
  120. }
  121. @Override
  122. public void removeAllkey(int key) {
  123. if (head == null) {
  124. // 链表为空,就抛异常
  125. throw new LinkedListIsEmptyException("链表为空异常!");
  126. }else if (head.next == null) {
  127. // 链表里面只有一个元素
  128. if (head.val == key) {
  129. head = null;
  130. last = null;
  131. return;
  132. }else {
  133. throw new NotFindException("找不到要删除的数异常!");
  134. }
  135. }else {
  136. boolean flag = false;
  137. // 头删
  138. while (head.val == key) {
  139. flag = true;
  140. head = head.next;
  141. if (head == null) {
  142. return;
  143. }
  144. }
  145. while (last.val == key) {
  146. // 尾删
  147. flag = true;
  148. last = last.prev;
  149. last.next = null;
  150. }
  151. ListNode cur = head;
  152. while (cur != null) {
  153. if (cur.val == key) {
  154. // 开始删除
  155. flag = true;
  156. ListNode curNext = cur.next;
  157. cur.prev.next = curNext;
  158. curNext.prev = cur.prev;
  159. }
  160. cur = cur.next;
  161. }
  162. if (!flag) {
  163. throw new NotFindException("找不到要删除的数异常!");
  164. }
  165. }
  166. }
  167. @Override
  168. public int size() {
  169. ListNode cur = head;
  170. int count = 0;
  171. while (cur != null) {
  172. count++;
  173. cur = cur.next;
  174. }
  175. return count;
  176. }
  177. @Override
  178. public void clear() {
  179. // 暴力做法
  180. // head = last = null;
  181. // 温柔做法
  182. ListNode cur = head;
  183. while (cur != null) {
  184. // cur.val = null;
  185. cur.prev = null;
  186. ListNode curNext = cur.next;
  187. cur.next = null;
  188. cur = curNext;
  189. }
  190. head = last = null;
  191. }
  192. @Override
  193. public void display() {
  194. ListNode cur = head;
  195. while (cur != null) {
  196. System.out.print(cur.val+" ");
  197. cur = cur.next;
  198. }
  199. System.out.println();
  200. }
  201. }

 addlndexOfException异常:

  1. public class addIndexOfException extends RuntimeException {
  2. public addIndexOfException(String msg) {
  3. super(msg);
  4. }
  5. public addIndexOfException() {
  6. super();
  7. }
  8. }

LinkedListlsEmptyException异常:

  1. public class LinkedListIsEmptyException extends RuntimeException {
  2. public LinkedListIsEmptyException(String msg) {
  3. super(msg);
  4. }
  5. public LinkedListIsEmptyException() {
  6. super();
  7. }
  8. }

 NotFindException异常:

  1. public class NotFindException extends RuntimeException {
  2. public NotFindException(String msg) {
  3. super(msg);
  4. }
  5. public NotFindException() {
  6. super();
  7. }
  8. }

单链表和双链表在代码上,总体来说,大差不差。

分析 LinkedList 的源码 

LinkedList的底层使用了双向链表 

LinkedList的使用 

构造方法与 ArrayList 差不多,只有一个无参的和一个带参数的。带参的类型是实现了Collection接口就行。

常用方法 

方法说明
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 lastlndexOf(Object o)返回最后一个o的下标
List<E> subList(int fromlndex, int tolndex)截取部分list 

同样这里 subList 的截取不会产生新的对象。

其遍历方式和 ArrayList 是一样的。

ArrayList和LinkedList的区别

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

好啦!本期 数据结构之LinkedList与链表(上)的学习之旅就到此结束啦!我们下一期再一起学习吧!

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

闽ICP备14008679号