当前位置:   article > 正文

Java链表详解--通俗易懂(超详细,含源码)

java链表

目录

概念

链表的分类

链表的结构

代码实现链表

1.创建节点类

2.创建链表

方法一:枚举法

方法二:头插法public void addFirst(int data)

方法三:尾插法public void addLast(int data)

3.打印链表:public void display()

4.查找是否包含关键字key是否在单链表当中:public boolean contains(int key)

5.得到单链表的长度:public int Size()

6.任意位置插入,第一个数据节点为0号下标:public boolean addIndex(int index,int data)

7.删除第一次出现关键字为key的节点:public void remove(int key)

8.删除所有值为key的节点:public void removeAllKey(int key)

9.清空链表:public void clear()

源码


概念

链表(linked list):是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的.

链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

链表的分类

  • 单向链表,双向链表
  • 带头链表,不带头链表
  • 循环的,非循环的

排列组合后一共有

eq?C_%7B2%7D%5E%7B1%7D*C_%7B2%7D%5E%7B1%7D*C_%7B2%7D%5E%7B1%7D%3D8

即一共8种链表,其中单向、不带头、非循环以及双向、不带头、非循环的链表最为重要,也是本文主要介绍的链表类型。

链表的结构

对于链表的结构,可以用如下这个图来模拟。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Zmc5Zmc5Zmc5Zmc6bKB5YWI55Sf,size_9,color_FFFFFF,t_70,g_se,x_16

图中所示的为链表的一个节点,value是这个节点的所存储的数据值,next为下一节点的地址。

下面是一个5个节点的链表。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Zmc5Zmc5Zmc5Zmc6bKB5YWI55Sf,size_20,color_FFFFFF,t_70,g_se,x_16

接下来,我们来实现这样的链表的增删查改

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Zmc5Zmc5Zmc5Zmc6bKB5YWI55Sf,size_20,color_FFFFFF,t_70,g_se,x_16

第一个节点,地址假设是0x999,存储的数据是11,next存储的是下一个节点的地址(假设是0x888)

第二个节点,地址假设是0x888,存储的数据是22,next存储的是下一个节点的地址(假设是0x777)

第三个节点,地址假设是0x777,存储的数据是33,next存储的是下一个节点的地址(假设是0x666)

第四个节点,地址假设是0x666,存储的数据是44,next存储的是下一个节点的地址(假设是0x555)

第五个节点,地址假设是0x555,存储的数据是55,由于没有后续节点,next存储的是空指针null

定义一个head,存储头节点(第一个节点)的地址(假设为0x999)。

代码实现链表

1.创建节点类

节点由val域(数据域),以及next域(指针域)组成,对于next域,其是引用类型,存放下一个节点的地址,故

 用public ListNode next来创建next。

同时设置构造函数,方便对val进行初始化。

  1. //ListNode代表一个节点
  2. class ListNode{
  3. public int val;
  4. public ListNode next;
  5. //构造函数
  6. public ListNode(int a){
  7. this.val = a;
  8. }
  9. }

2.创建链表

  • 方法一:枚举法(略简单,略low)

  1. public class MyLinkedList {
  2. public ListNode head;//链表的头
  3. public void creatList(){
  4. ListNode listNode1 = new ListNode(11);
  5. ListNode listNode2 = new ListNode(22);
  6. ListNode listNode3 = new ListNode(33);
  7. ListNode listNode4 = new ListNode(44);
  8. ListNode listNode5 = new ListNode(55);
  9. this.head = listNode1;
  10. listNode1.next = listNode2;
  11. listNode2.next = listNode3;
  12. listNode3.next = listNode4;
  13. listNode4.next = listNode5;
  14. }
  15. }

直接进行val的赋值以及对next的初始化。

注意:不用对最后一个节点的next进行赋值,因为next是引用类型,不赋值则默认为null。

  • 方法二:头插法public void addFirst(int data)

头插法是指在链表的头节点的位置插入一个新节点,定义一个node表示该节点,然后就是对node的next进行赋值,用node.next = this.head即可完成(注意:head应指向新节点)

代码实现

  1. public void addFirst(int data){
  2. ListNode node = new ListNode(data);
  3. node.next = this.head;
  4. this.head = node;
  5. }
  • 方法三:尾插法public void addLast(int data)

尾插法是指在链表的尾节点的位置插入一个新节点,定义一个node表示该节点,然后就是对原来最后一个节点的next进行赋值,先将head移动至原来最后一个节点,用head.next = node进行赋值(注意,如果链表不为空,需要定义cur来代替head)

代码实现

  1. public void addLast(int data){
  2. ListNode node = new ListNode(data);
  3. if(this.head == null){
  4. this.head = node;
  5. }else {
  6. ListNode cur = this.head;
  7. while(cur.next != null){
  8. cur = cur.next;
  9. }
  10. cur.next = node;
  11. }
  12. }

3.打印链表:public void display()

认识了链表的结构,我们可以知道,节点与节点之间通过next产生联系。并且我们已将创建了head,即头节点的地址,通过head的移动来实现链表的打印。

注意:为了使head一直存在且有意义,我们在display()函数中定义一个cur:ListNode cur = this.head;来替代head。

对于head的移动,可用head = head.next来实现。

代码实现:

  1. public void display(){
  2. ListNode cur = this.head;
  3. while(cur != null){
  4. System.out.print(cur.val+" ");
  5. cur = cur.next;
  6. }
  7. System.out.println();
  8. }

4.查找是否包含关键字key是否在单链表当中:public boolean contains(int key)

查找key,可以利用head移动,实现对于key的查找(注意:同样要定义一个cur来代替head)

代码实现

  1. public boolean contains(int key){
  2. ListNode cur = this.head;
  3. while(cur != null){
  4. if(cur.val == key){
  5. return true;
  6. }
  7. cur = cur.next;
  8. }
  9. return false;
  10. }

5.得到单链表的长度:public int Size()

定义计数器count = 0,通过head的移动来判断链表长度(注意:同样要定义一个cur来代替head)

代码实现

  1. public int Size(){
  2. int count = 0;
  3. ListNode cur = this.head;
  4. while(cur != null){
  5. count++;
  6. cur = cur.next;
  7. }
  8. return count;
  9. }

6.任意位置插入,第一个数据节点为0号下标:public boolean addIndex(int index,int data)

比如,我们把一个值为1314,地址是0x520(设为node引用)的节点,即val域值为1314,next域为null,地址是520,将该节点插入至3号位置,

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Zmc5Zmc5Zmc5Zmc6bKB5YWI55Sf,size_20,color_FFFFFF,t_70,g_se,x_16

 

经过分析,需要将head先移至2号位置注意:用cur代替head,防止head丢失),然后

node.next = cur.next使该节点的next域改为下一节点的地址,再cur.next = node.next使前一节点

的next域改为该节点的地址。

  1. public void addIndex(int index,int data){
  2. if(index < 0 ||index > Size()){ //对index位置的合法性进行判断
  3. return;
  4. }
  5. if(index == 0){ //相当于头插法
  6. addFirst(data);
  7. return;
  8. }
  9. if(index = Size()){ //相当于尾插法
  10. addLast(data);
  11. return;
  12. }
  13. ListNode cur = findIndex(index);//找到index位置前一位置的地址
  14. ListNode node = new ListNode(data);//初始化node
  15. node.next = cur.next;
  16. cur.next = node;
  17. }

7.删除第一次出现关键字为key的节点:public void remove(int key)

对于删除第一次出现的key值的节点,若不是头节点,我们只需将key值对应的节点的前一节点的next的域改为key值对应的节点的next域即可。

对于头节点,直接head = head.next即可。

对于key值对应的节点的前一节点,我们可以写一个函数来找到它,方便后续的代码书写。

  1. //找到key的前驱(前一节点)
  2. public ListNode searchPrev(int key){
  3. ListNode cur = this.head;
  4. while(cur.next != null){
  5. if(cur.next.val == key){
  6. return cur;
  7. }
  8. cur = cur.next;
  9. }
  10. return null;
  11. }
  12. //删除第一次出现关键字为key的节点
  13. public void remove(int key){
  14. if(this.head == null){
  15. return;
  16. }
  17. if(this.head.val == key){
  18. this.head = this.head.next;
  19. return;
  20. }
  21. ListNode cur = searchPrev(key);
  22. if(cur == null){
  23. return; //没有要删除的节点
  24. }
  25. ListNode del = cur.next;//定义要删除的节点
  26. cur.next = del.next;
  27. }

8.删除所有值为key的节点:public void removeAllKey(int key)

若要删除所有值为key的节点,其实我们只需多次调用上面所写的remove函数即可完成,但是,

若要达到面试难度,那么要求就是遍历一遍链表,删除所有值为key的节点。

 

情况一:key连续,如下(1,2,3节点)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Zmc5Zmc5Zmc5Zmc6bKB5YWI55Sf,size_20,color_FFFFFF,t_70,g_se,x_16

 

情况二:key不连续,如下(1,3节点)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Zmc5Zmc5Zmc5Zmc6bKB5YWI55Sf,size_20,color_FFFFFF,t_70,g_se,x_16

代码实现:

  1. public ListNode removeAllKey(int key){
  2. if(this.head = null){
  3. return null;
  4. }
  5. ListNode prev = this.head;
  6. ListNode cur = this.head.next;
  7. while(cur != null){
  8. if(cur.val == key){
  9. prev.next = cur.next;
  10. cur = cur.next;
  11. }else {
  12. prev = cur;
  13. cur = cur.next;
  14. }
  15. }
  16. if(this.head.val == key){
  17. this.head = this.head.next;
  18. }
  19. return this.head;
  20. }

 

9.清空链表:public void clear()

1.简单粗暴的方法:将头节点置为空head = null;即可

2.细腻温柔的做法:将每一个节点都置为空

  1. public void clear(){
  2. while(this.head != null){
  3. ListNode curNext = this.head.next;
  4. this.head.next = null;
  5. this.head = curNext;
  6. }
  7. }

源码

  1. import java.util.List;
  2. //ListNode代表一个节点
  3. class ListNode{
  4. public int val;
  5. public ListNode next;
  6. //构造函数
  7. public ListNode(int a){
  8. this.val = a;
  9. }
  10. }
  11. public class MyLinkedList {
  12. public ListNode head;//链表的头
  13. public void creatList() {
  14. ListNode listNode1 = new ListNode(11);
  15. ListNode listNode2 = new ListNode(22);
  16. ListNode listNode3 = new ListNode(33);
  17. ListNode listNode4 = new ListNode(44);
  18. ListNode listNode5 = new ListNode(55);
  19. this.head = listNode1;
  20. listNode1.next = listNode2;
  21. listNode2.next = listNode3;
  22. listNode3.next = listNode4;
  23. listNode4.next = listNode5;
  24. }
  25. //头插法
  26. public void addFirst(int data) {
  27. ListNode node = new ListNode(data);
  28. node.next = this.head;
  29. this.head = node;
  30. /*if(this.head == null){
  31. this.head = node;
  32. }else{
  33. node.next = this.head;
  34. this.head = node;
  35. }*/
  36. }
  37. //尾插法
  38. public void addLast(int data) {
  39. ListNode node = new ListNode(data);
  40. if (this.head == null) {
  41. this.head = node;
  42. } else {
  43. ListNode cur = this.head;
  44. while (cur.next != null) {
  45. cur = cur.next;
  46. }
  47. cur.next = node;
  48. }
  49. }
  50. //打印顺序表
  51. public void display() {
  52. ListNode cur = this.head;
  53. while (cur != null) {
  54. System.out.print(cur.val + " ");
  55. cur = cur.next;
  56. }
  57. System.out.println();
  58. }
  59. //查找是否包含关键字key是否在单链表当中
  60. public boolean contains(int key) {
  61. ListNode cur = this.head;
  62. while (cur != null) {
  63. if (cur.val == key) {
  64. return true;
  65. }
  66. cur = cur.next;
  67. }
  68. return false;
  69. }
  70. //得到单链表的长度
  71. public int Size() {
  72. int count = 0;
  73. ListNode cur = this.head;
  74. while (cur != null) {
  75. count++;
  76. cur = cur.next;
  77. }
  78. return count;
  79. }
  80. //找到index位置的前一位置的地址
  81. public ListNode findIndex(int index) {
  82. ListNode cur = head.next;
  83. while (index - 1 != 0) {
  84. cur = cur.next;
  85. index--;
  86. }
  87. return cur;
  88. }
  89. //任意位置插入,第一个数据节点为0号下标
  90. public void addIndex(int index, int data) {
  91. if (index < 0 || index > Size()) {
  92. return;
  93. }
  94. if (index == 0) { //相当于头插法
  95. addFirst(data);
  96. return;
  97. }
  98. if (index == Size()) { //相当于尾插法
  99. addLast(data);
  100. return;
  101. }
  102. ListNode cur = findIndex(index);//找到index位置前一位置的地址
  103. ListNode node = new ListNode(data);//初始化node
  104. node.next = cur.next;
  105. cur.next = node;
  106. }
  107. //找到key的前驱(前一节点)
  108. public ListNode searchPrev(int key) {
  109. ListNode cur = this.head;
  110. while (cur.next != null) {
  111. if (cur.next.val == key) {
  112. return cur;
  113. }
  114. cur = cur.next;
  115. }
  116. return null;
  117. }
  118. //删除第一次出现关键字为key的节点
  119. public void remove(int key) {
  120. if (this.head == null) {
  121. return;
  122. }
  123. if (this.head.val == key) {
  124. this.head = this.head.next;
  125. return;
  126. }
  127. ListNode cur = searchPrev(key);
  128. if (cur == null) {
  129. return; //没有要删除的节点
  130. }
  131. ListNode del = cur.next;//定义要删除的节点
  132. cur.next = del.next;
  133. }
  134. //删除所有值为key的节点
  135. public ListNode removeAllKey(int key) {
  136. if (this.head = null) {
  137. return null;
  138. }
  139. ListNode prev = this.head;
  140. ListNode cur = this.head.next;
  141. while (cur != null) {
  142. if (cur.val == key) {
  143. prev.next = cur.next;
  144. cur = cur.next;
  145. } else {
  146. prev = cur;
  147. cur = cur.next;
  148. }
  149. }
  150. if (this.head.val == key) {
  151. this.head = this.head.next;
  152. }
  153. return this.head;
  154. }
  155. //清空链表
  156. public void clear() {
  157. while (this.head != null) {
  158. ListNode curNext = this.head.next;
  159. this.head.next = null;
  160. this.head = curNext;
  161. }
  162. }
  163. }

 

 

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

闽ICP备14008679号