链表是什么?

链表是有序的列表,是代码中常用的一种链式存取的数据结构,它是以节点的方式来存储数据。
每个节点包含data域和next域,data用来存放数据,next用来指向下一个节点的地址。

如图所示,
image.png
最后一个节点的 next域为null

虽然在逻辑结构中,链表是一个个节点连接起来的,但实际在内存中,它是离散存储的。

一般的我们会单独创建一个头节点用来遍历整个链表,头节点中不存放数据信息,只存放第一个节点的位置

链表的基础操作

添加

要添加一个新节点首先你要找到最后一个节点,然后将其next指向 当前这个新节点就可以
这个简单,你只需要从头节点进行遍历就行了
但是要注意的一点是,头节点不能移动!!!!
所以我们必须创建一个辅助节点,用这个辅助接点去遍历整个 链表。
image.png
如图,用temp 代替头节点来遍历,temp = temp.next 知道 temp 等于最后一个节点,也就是 temp.next == null
这个时候,再让 temp.next 指向新节点

如果需要按照顺序进行
我们一定要找到插入的位置
image.png
这个位置应该怎么找呢?一定是要找到 要加入位置的前一个节点(按照升序排列的情况下,降序则反之)
比如,你现在的node.no是3,要加入的位置是链表中的no是2,和no是4的两个节点,你就需要找到2这个位置,而2.next.no此时是4

所以我们用2.next.no来与加入的节点的 node.no判断
最后将节点信息进行改变
先将节点3.next 指向节点4
node.next = temp.next;
再将节点2.next 指向 节点3
temp.next = node;

遍历

遍历就很简单了,一直让 temp = temp.next就行了,知道找到最后一个节点,也就是temp.next == null

修改

修改的时候也需要遍历,而我们不需要修改next中信息,只需要修改data域中的信息就可以了

删除

删除与添加基本一致,要找到删除那个节点的前一个节点,毕竟只有它有要删除节点的内存地址,这个时候我们只需要让它指向,要删除这个节点的后一个位置即可

temp.next = temp.next.next

image.png

代码实现

  1. public class SingleLinkedListDemo {
  2. public static void main(String[] args) {
  3. //创建链表
  4. SingleLinkedList list = new SingleLinkedList();
  5. //创建节点数据
  6. HeroNode node1 = new HeroNode(1,"宋江","及时雨");
  7. HeroNode node2 = new HeroNode(2,"卢俊义","玉麒麟");
  8. HeroNode node3 = new HeroNode(3,"吴用","智多星");
  9. // list.add(node1);
  10. // list.add(node3);
  11. // list.add(node2);
  12. list.addByOrder(node1);
  13. list.addByOrder(node3);
  14. list.addByOrder(node2);
  15. list.list();
  16. list.update(new HeroNode(3, "无用", "智多星"));
  17. System.out.println("修改后的链表信息");
  18. list.list();
  19. list.delete(node1);
  20. list.delete(node2);
  21. list.delete(node3);
  22. System.out.println("删除后的链表信息");
  23. list.list();
  24. }
  25. }
  26. class SingleLinkedList {
  27. //这是一个头节点,头节点不必存放信息
  28. private HeroNode head = new HeroNode(0,"","");
  29. //向链表中添加新元素
  30. public void add(HeroNode node) {
  31. //用一个临时的辅助指针来代替 头指针进行遍历
  32. HeroNode temp = head;
  33. //循环遍历到最后一个节点
  34. while (true) {
  35. //如果temp.next == null,说明当前节点为最后一个节点
  36. if (temp.next == null) {
  37. break;
  38. }
  39. // temp 指针后移,指向下一个节点
  40. temp = temp.next;
  41. }
  42. //将最后一个节点 next指向新的元素
  43. temp.next = node;
  44. }
  45. public void addByOrder(HeroNode node) {
  46. //用一个临时的辅助指针来代替 头指针进行遍历
  47. HeroNode temp = head;
  48. //用来判断是否找到 要修改节点
  49. boolean flag = false;
  50. //循环遍历到最后一个节点
  51. while (true) {
  52. //如果temp.next == null,说明当前节点为最后一个节点
  53. if (temp.next == null) {
  54. break;
  55. }
  56. //如果要添加节点的编号已存在于链表中
  57. if (temp.next.no == node.no) {
  58. flag = true;
  59. break;
  60. }
  61. //想要按照顺序排列,我们一定要找到插入的位置
  62. //这个位置应该怎么找呢?一定是要找到 要加入位置的前一个节点(按照升序排列的情况下,降序则反之)
  63. // 比如,你现在的no是3,要加入的位置是链表中的no是2,和no是4的两个节点,你就需要找到2这个位置,而2.next.no此时是4
  64. //所以我们用2.next.no来与加入的节点的.no判断
  65. if (temp.next.no > node.no) {
  66. //先将节点3.next 指向节点4
  67. node.next = temp.next;
  68. //再将节点2.next 指向 节点3
  69. temp.next = node;
  70. break;
  71. }
  72. // temp 指针后移,指向下一个节点
  73. temp = temp.next;
  74. }
  75. if (flag) {
  76. System.out.println("要添加的节点编号已存在于当前链表");
  77. return;
  78. }
  79. //将最后一个节点 next指向新的元素
  80. temp.next = node;
  81. }
  82. //遍历当前这个链表
  83. public void list() {
  84. if (head.next == null) {
  85. System.out.println("当前链表为空");
  86. return;
  87. }
  88. //这里我们不需要输出头节点,所以我们可以直接让辅助指针指向第一个节点
  89. HeroNode temp = head.next;
  90. while (true) {
  91. //这里因为我们要输出最后一个元素,所以不能 判断 temp.next == null
  92. if (temp == null) {
  93. break;
  94. }
  95. System.out.println(temp);
  96. temp = temp.next;
  97. }
  98. }
  99. //根据节点的 no进行修改
  100. public void update(HeroNode node) {
  101. if (head.next == null) {
  102. System.out.println("链表为空");
  103. return;
  104. }
  105. HeroNode temp = head.next;
  106. //用来判断是否找到 要修改节点
  107. boolean flag = false;
  108. //找到需要修改的节点
  109. while(true) {
  110. if (temp == null) {
  111. //已经遍历完成
  112. break;
  113. }
  114. if (temp.no == node.no) {
  115. //找到该节点
  116. flag = true;
  117. break;
  118. }
  119. temp = temp.next;
  120. }
  121. if (flag) {
  122. temp.name = node.name;
  123. temp.nickName = node.nickName;
  124. } else {
  125. System.out.println("没有找到编号为"+node.no+"节点");
  126. }
  127. }
  128. public void delete(HeroNode node) {
  129. if (head.next == null) {
  130. System.out.println("链表为空");
  131. return;
  132. }
  133. HeroNode temp = head;
  134. boolean flag = false;
  135. while(true) {
  136. if (temp.next == null) {
  137. break;
  138. }
  139. if (temp.next.no == node.no) {
  140. //找到该节点
  141. flag = true;
  142. break;
  143. }
  144. temp = temp.next;
  145. }
  146. if (flag) {
  147. temp.next = temp.next.next;
  148. } else {
  149. System.out.println("没有找到编号为"+node.no+"节点");
  150. }
  151. }
  152. }
  153. class HeroNode {
  154. int no;
  155. String name;
  156. String nickName;
  157. //指向下一个节点
  158. HeroNode next;
  159. public HeroNode(int no, String name, String nickName) {
  160. this.no = no;
  161. this.name = name;
  162. this.nickName = nickName;
  163. }
  164. @Override
  165. public String toString() {
  166. return "[HeroNode no=" + no + ", name=" + name + ", nickName=" + nickName + "]" ;
  167. }
  168. }

获取单链表的倒数第k个节点

通常方法

首先说一下思路,
我们首先要知道当前链表中的有效个数是多少
那也就是要遍历一下当前链表,然后通过计数器找到有效个数size

既然是要找倒数第k个节点,那么毫无疑问,还是要再进行遍历一次
辅助节点指向第一个节点的时候,temp = head.nex
只需要遍历 size - k 次即可
辅助节点仍指向头节点的话, 要循环size - k + 1 次

代码

  1. //获取有效节点的个数
  2. public int getSize() {
  3. int size = 0;
  4. HeroNode temp = head.next;
  5. while ( temp != null) {
  6. size ++;
  7. temp = temp.next;
  8. }
  9. return size;
  10. }
  11. //获取倒数第 k 个节点
  12. public HeroNode findLastIndexNode(int index) {
  13. if (head.next == null) {
  14. return null;
  15. }
  16. HeroNode temp = head.next;
  17. int size = getSize();
  18. //验证 index 有效性
  19. if (index <= 0 || index > size) {
  20. return null;
  21. }
  22. for (int i = 0; i < size - index; i++) {
  23. temp = temp.next;
  24. }
  25. return temp;
  26. }

快慢指针的方法来获取倒数第k个节点

首先要设置两个辅助节点
temp1 = head
temp2 = head

  • 首先 temp1不动

    1. temp2 先找到第 k 个节点( 包括头节点在内 )
  • temp1 和 temp2 一起遍历,直到 temp2指向最后一个节点

    1. 这时的 temp1指向的就是倒数第 k 个节点

如图:
快慢指针找倒数第k个节点.gif

反转链表

在反转链表时,我们可以采用头插法和栈这两种方法,这里使用的时头插法

什么是头插法?
就是将新加入的节点的next 指向 ->原来的 头节点next 域指向的节点
然后将头节点的next 域 指向 新加入的节点
头插法.gif

而在反转链表时,情况比这要复杂。
因为我们必须要记录下一个节点的信息。
这个时候我们就需要多个辅助节点了

  • temp 依然指向指向遍历的当前这个节点
  • next 指向 当前这个节点的下一个元素
  • reverseHead 用来作为 反转链表的头节点

在进行反转的时候
我们首先要将下一个节点的信息存储起来
next = temp.next
然后进行头插法
temp.next = reverseHead.next
reverseHead.next = temp
最后我们通过 next节点来进行下一个循环,也就是temp后移
temp = next

具体代码

  1. //将单链表进行反转
  2. //利用头插法
  3. public void reverse() {
  4. //如果链表为 null 或者只有一个节点,说明不需要反转
  5. if (head.next == null || head.next.next == null) {
  6. return;
  7. }
  8. HeroNode temp = head.next;
  9. System.out.println(temp == head.next);
  10. //用来表示反转链表的 头节点
  11. HeroNode reverseHead = new HeroNode(0,"","");;
  12. //指向当前节点的下一个节点
  13. HeroNode next = null;
  14. while (temp != null) {
  15. //先把下一个节点的信息保存下来
  16. next = temp.next;
  17. //将反转链表头节点的 下一个节点保存到 当前节点的next 域里
  18. temp.next = reverseHead.next;
  19. //将当前节点放到 头节点的 next域里
  20. reverseHead.next = temp;
  21. //读取下个节点的信息
  22. temp = next;
  23. }
  24. head.next = reverseHead.next;
  25. }

合并两个有序链表

(升序)思路:
首先我们肯定需要创建一个新的链表 newList 以及其辅助节点 newTemp指向其头节点

其次我们还要额外设置两个辅助节点
一个叫 temp1 用来遍历第一个链表
一个叫 temp2 用来遍历第二个链表
可以直接指向第一个节点。

如果第一个链表比第二个链表小
那么这个时候我们需要将 第一个链表中的节点放入到新链表中
然后第一个链表和新的链表都向后移动一个节点
newTemp.next = temp1.next
temp1 = temp1.next
newTemp = newTemp.next

第二个节点同理

然后还有一个关键点!如果第一个链表先遍历完了,但是第二链表还没遍历完,这个时候我们可以将第二个链表的剩余节点,直接放入新链表中。

if (temp1 == null) {
newTemp.next = temp2;
}

第二个链表同理

具体代码实现

  1. public static SingleLinkedList merge(SingleLinkedList list1, SingleLinkedList list2) {
  2. //合并的新链表
  3. SingleLinkedList newList = new SingleLinkedList();
  4. //新链表的辅助接点
  5. HeroNode newTemp = newList.head;
  6. //需要两个辅助节点,指向头节点
  7. HeroNode temp1 = list1.head.next;
  8. HeroNode temp2 = list2.head.next;
  9. //现在按升序排列
  10. while(true) {
  11. if ( temp1 == null || temp2 == null) {
  12. break;
  13. }
  14. if (temp1.no <= temp2.no) {
  15. newTemp.next = temp1;
  16. temp1 = temp1.next;
  17. newTemp = newTemp.next;
  18. continue;
  19. }
  20. if (temp1.no > temp2.no) {
  21. newTemp.next = temp2;
  22. temp2 = temp2.next;
  23. newTemp = newTemp.next;
  24. }
  25. }
  26. if (temp1 == null) {
  27. newTemp.next = temp2;
  28. }
  29. if (temp2 == null) {
  30. newTemp.next = temp1;
  31. }
  32. return newList;
  33. }