• 数据结构之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 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 subList(int fromlndex, int tolndex)截取部分list 

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

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

    ArrayList和LinkedList的区别

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

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

  • 相关阅读:
    微信小程序:用户基本信息的采集
    每日论文阅读 2022-11-11
    金仓数据库KingbaseES客户端应用参考手册--17. vacuumdb
    语义推理的功能组件动态绑定研究
    Centos 安装 OpenLDAP
    问一下ChatGPT如何学习开发iOS应用程序
    C++ 变量的声明和初始化方式
    React跨路由组件动画
    分库分表订单全局ID
    Git常用指令-1
  • 原文地址:https://blog.csdn.net/2301_80854132/article/details/139563576