一、单向链表

1、基本介绍

  1. 链表是有序的列表,但是他再内存中的存储如下 —————->
  2. 链表是以节点的方式来存储数据的,是链式存储
  3. 每个节点包含data域(存数据) next域(存下一个节点的地址)
  4. 链表中各个节点不一定是连续存储。(可以充分利用碎片内存)
  5. 链表分为 带头节点链表不带头节点链表,根据实际需求来确定

未命名图片.png

2、带头节点的链表

未命名图片.png

  1. head节点不存放任何具体数据,作用就是表示单链表的头。
  2. 最后一个节点的next域 存放的是null,表示这是尾部
  3. 从单项链表中添加一个节点的思路
    • 直接在添加时添加到链表的尾部
      • 首先判断链表是否为空 head.next == null
      • 创建一个temp辅助变量,temp = head
      • 遍历时 如果当 temp.next = null, 说明跳出循环,进行添加 temp.next = 新节点
      • 每循环一次,temp向后移动一位
    • 根据id的大小,按顺序添加到链表中
      • 首先判断是否链表是否为空
      • 需要一个temp辅助变量 temp = head
      • 首先需要搞清楚,如果 现在链表中有 1 3 两个节点,你需要将2 添加进去
      • 在循环中 如果 **te**``**mp.next.id > 新节点.id ** 则表示可以将节点插入到 temp 和 temp.next 之间,break;退出循环
      • 使用一个辅助变量 flag ,表示id是重复,就给赋值上true
      • 在循环中,如果**temp.next.id == 新节点.id** 则表示id重复,不能进行插入 falg = true
      • 每循环一次,temp向后移动一位
      • 判断如果为false 就给插入 **//2.next = 1.next; 1.next = 2;** 这样 2 就被插入到 1 和 3的中间了
      • 如果是true 给出提示信息,表示id重复

未命名图片.png

  1. 根据id更新一个节点的数据,不更新 id 和 next ,其余都可以更新 的思路
    • 先遍历 (遍历前先判断链表是否为空)
    • 在循环中 判断 if(temp.next.id == 新节点.id) 找到了就退出循环
    • 没循环一次 temp就往后移动一位
    • 可以使用一个flag变量 来根据变量判断 是否找到了 如果为true则更新 为false表示没找到
  2. 删除节点思路
    • 我们先找到需要删除的这个节点的 前面一个节点 temp
    • 然后 temp.next = temp.next.next 这样就删除了中间的那个节点
    • 举例:1 2 3 个节点,1的下一个节点是 1.next.next 则 1.next = 3 ,这样2 就没了
    • 被删除的节点没有了其他引用的指向,会被垃圾回收器机制回收

未命名图片.png

二、单项链表代码实现

  1. package com.yixuexi.linkedList;
  2. import java.util.Stack;
  3. /**
  4. * 2020/10/10 20:10
  5. */
  6. //模拟带头节点的单向链表
  7. public class HeadLinkedListDemo {
  8. public static void main(String[] args) {
  9. //进行测试
  10. //首先创建几个人物数据
  11. HeroNode n1 = new HeroNode(1,"松江","及时雨");
  12. HeroNode n2 = new HeroNode(2,"李逵","黑旋风");
  13. HeroNode n3 = new HeroNode(3,"吴用","智多星");
  14. HeroNode n4 = new HeroNode(4,"卢俊义","玉麒麟");
  15. HeroNode n5 = new HeroNode(5,"武大郎","烧饼哥");
  16. //创建一个链表
  17. HeadLinkedList headLinkedList = new HeadLinkedList();
  18. //往链表中添加数据
  19. headLinkedList.add(n1);
  20. headLinkedList.add(n2);
  21. headLinkedList.add(n5);
  22. //查看链表中的数据
  23. headLinkedList.list();
  24. //往链表中添加数据
  25. headLinkedList.addByOrder(n3);
  26. headLinkedList.addByOrder(n4);
  27. headLinkedList.addByOrder(n4);
  28. //分割线
  29. System.out.println("------------------------------------------------->");
  30. //再次查看链表中的数据
  31. headLinkedList.list();
  32. //分割线
  33. System.out.println("------------------------------------------------->");
  34. //更新链表中的数据
  35. HeroNode newn5 = new HeroNode(5,"武松","打虎哥");
  36. HeroNode newn6 = new HeroNode(6,"武松","打虎哥");
  37. headLinkedList.update(newn5);
  38. headLinkedList.update(newn6);
  39. //再次查看链表中的数据
  40. headLinkedList.list();
  41. //分割线
  42. System.out.println("------------------------------------------------->");
  43. //删除链表中的数据
  44. headLinkedList.delete(2);
  45. //查看删除之后的数据
  46. headLinkedList.list();
  47. //查看链表中有效数据的个数
  48. System.out.println(HeadLinkedList.getLength(headLinkedList.getHead()));
  49. //查看链表中倒数第2个节点
  50. HeroNode last = HeadLinkedList.getLast(headLinkedList.getHead(), 2);
  51. System.out.println(last);
  52. //分割线
  53. System.out.println("------------------------------------------------->");
  54. //反转链表
  55. //HeadLinkedList.reverse(headLinkedList.getHead());
  56. //headLinkedList.list();
  57. //分割线
  58. System.out.println("------------------------------------------------->");
  59. // 反向打印链表
  60. HeadLinkedList.reversePrint(headLinkedList.getHead());
  61. }
  62. }
  63. //创建一个单向链表类,用来管理单项链表
  64. class HeadLinkedList {
  65. //在这个单项链表类中有一个 头节点,这个头节点不能动
  66. private HeroNode head = new HeroNode(0, "", "");
  67. public HeroNode getHead() {
  68. return head;
  69. }
  70. /**
  71. * 此方法 添加数据的时候没有顺序,是按照先添加的在前面,后添加的在后面的顺序
  72. *
  73. * @param heroNode
  74. */
  75. public void add(HeroNode heroNode) {
  76. //由于每次添加节点都是往链表的最后去添加,所以先要遍历一遍链表,找出最后那个元素
  77. //这里遍历我们使用一个 temp临时变量 辅助我们遍历
  78. //这里这个temp就是头节点,使用循环,如果temp.next == null,则表示它是最后一个节点
  79. //如果 temp.next != null 则表示它不是最后一个节点,则往后移一位, temp = temp.next;
  80. HeroNode temp = head;
  81. while (true) {
  82. if (temp.next == null) {
  83. //说明此时 temp是最后一个节点 直接跳出循环
  84. break;
  85. }
  86. //能执行到这里,则表示不是最后一个节点,往后移动一位,继续循环
  87. temp = temp.next;
  88. }
  89. //执行到这里,说明此时的temp一定为最后一个节点,直接将节点添加到 temp.next 即可
  90. temp.next = heroNode;
  91. }
  92. /**
  93. * 按照id的顺序进行添加
  94. * 添加的时候按照数据 的id 进行排序,id 为 1 的在空节点后面 1 ->> 2 ->> 3 ->> 4
  95. */
  96. public void addByOrder(HeroNode heroNode) {
  97. //假设:现在链表中的数据为 1 3 4
  98. //现在 需要将2 插入到 1 和 3 的中间去
  99. //因为头节点不能动,所以需要一个temp辅助变量 来帮助我们遍历
  100. HeroNode temp = head;
  101. //需要使用一个flag辅助我们 判断是否插入的数据id已经存在了
  102. Boolean flag = false;
  103. //首先判断,如果这个链表为空,则直接添加在链表的后面即可
  104. if (temp.next == null) {
  105. temp.next = heroNode;
  106. return;
  107. }
  108. //循环 找到插入数据的位置 假设 将 2 插入到 1 3 中间
  109. // 则 2.next = 1.next; 1.next = 2; 这样 2 就被插入到 1 和 3的中间了
  110. while (true) {
  111. //这样则表示需要插入的位置找到了,就在 temp 和 temp.next 的中间
  112. if (temp.next.id > heroNode.id) {
  113. break;
  114. } else if (temp.next.id == heroNode.id) { //表示插入的数据id在链表中已经存在了
  115. flag = true;
  116. break;
  117. }
  118. //如果上面都没有实现则 temp向后移动一位
  119. temp = temp.next;
  120. }
  121. // 将节点插入到指定位置 temp 和 temp.next之间
  122. if (flag) {
  123. System.out.println("你要添加节点的id已经存在 id = " + heroNode.id + " 无法添加");
  124. } else {
  125. // 则 2.next = 1.next; 1.next = 2; 这样 2 就被插入到 1 和 3的中间了
  126. heroNode.next = temp.next;
  127. temp.next = heroNode;
  128. }
  129. }
  130. /**
  131. * 根据id 对节点的姓名和绰号进行更新
  132. *
  133. * @param newHeroNode
  134. */
  135. public void update(HeroNode newHeroNode) {
  136. //需要使用一个辅助变量 temp
  137. HeroNode temp = head;
  138. //首先判断是否为空,如果为空直接退出
  139. if (temp.next == null) {
  140. System.out.println("链表为空,请加入节点后在进行更新!");
  141. return;
  142. }
  143. //使用循环找出id为 newHeroNode.id 的节点
  144. Boolean flag = false;
  145. while (true) {
  146. if (temp == null) {
  147. break;
  148. }
  149. //如果找到了 那么直接跳出循环,因为当前temp 就是需要修改的节点
  150. if (temp.id == newHeroNode.id) {
  151. flag = true;
  152. break;
  153. }
  154. //没有找到则向后移动一位
  155. temp = temp.next;
  156. }
  157. //判断 如果flag为true 则表示当前temp是需要修改的
  158. if (flag) {
  159. temp.name = newHeroNode.name;
  160. temp.nickname = newHeroNode.nickname;
  161. } else {
  162. System.out.println("没有找到对应id的节点,请输入正确的数据");
  163. }
  164. }
  165. //删除节点
  166. public void delete(int id) {
  167. //辅助变量
  168. HeroNode temp = head;
  169. // 标记
  170. boolean flag = false;
  171. //思路:找到要删除的节点的前面那个 节点 temp
  172. //然后 temp.next = temp.next.next 这样中间那个节点的指向就没了,垃圾回收器就回收了
  173. while (true) {
  174. if (temp.next == null) {
  175. return;
  176. }
  177. if (temp.next.id == id) {
  178. flag = true;
  179. break;
  180. }
  181. // 如果都没有 则向后移动一位
  182. temp = temp.next;
  183. }
  184. if (flag) {
  185. temp.next = temp.next.next;
  186. } else {
  187. System.out.println("没有找到对应的节点id : " + id);
  188. }
  189. }
  190. //用来展示链表中的数据
  191. public void list() {
  192. //使用一个temp辅助变量来帮助我们进行循环遍历
  193. HeroNode temp = head;
  194. //首先判断一下这个链表是否为空链表
  195. //如果头节点的next域 为空则表示没有下一个节点,为空链表
  196. if (temp.next == null) {
  197. return;
  198. }
  199. // 因为上面做了判断了,所以直接输出就行
  200. //然后把temp往后移动一位,如果temp.next为空,则表示到头了
  201. while (true) {
  202. System.out.println(temp.next);
  203. temp = temp.next;
  204. if (temp.next == null) {
  205. break;
  206. }
  207. }
  208. }
  209. //用来查看链表中有效数据的个数
  210. //传过来一个头节点,即可得到他的长度
  211. public static int getLength(HeroNode head){
  212. //判断如果这个链表是空链表的话 直接return 0 就行
  213. if (head.next == null){
  214. return 0;
  215. }
  216. //因为不算上 头节点,所以是 head.next
  217. HeroNode temp = head.next;
  218. int length = 0;
  219. while ( temp != null){
  220. length ++;
  221. temp = temp.next;
  222. }
  223. return length;
  224. }
  225. //用来查看链表中倒数第K个节点
  226. public static HeroNode getLast(HeroNode head, int index){
  227. //1.首先根据头节点 得到链表的长度
  228. //判断如果这个链表是空链表的话 直接return 0 就行
  229. if (head.next == null){
  230. return null;
  231. }
  232. //因为不算上 头节点,所以是 head.next
  233. HeroNode temp = head.next;
  234. int length = 0;
  235. while ( temp != null){
  236. length ++;
  237. temp = temp.next;
  238. }
  239. //2. 做一个数据的校验,判断index是否合理
  240. if(index <= 0 || index > length){
  241. return null;
  242. }
  243. //3. 进行遍历,倒数第几个 : 长度 - 倒数第几个 = 要找的那个
  244. HeroNode temp2 = head; //辅助变量,帮我们遍历
  245. for(int i = 0; i <= length - index; i++){
  246. if (temp2.next == null){
  247. break;
  248. }
  249. temp2 = temp2.next;
  250. }
  251. return temp2;
  252. }
  253. //传入一个头节点,对其进行链表反转
  254. public static void reverse(HeroNode head){
  255. //如果链表为空 或者 链表中就一个元素,那么直接返回这个链表就行,不用反转
  256. if (head.next == null || head.next.next == null){
  257. return;
  258. }
  259. //定义一个辅助变量,帮助我们遍历链表
  260. HeroNode cur = head.next;
  261. //此变量永远指向 cur的下一个节点
  262. HeroNode next = null;
  263. //新的头节点
  264. HeroNode reverseHead = new HeroNode(0,"","");
  265. while (cur != null){
  266. //保存cur的下一个节点,防止丢失
  267. next = cur.next;
  268. //将cur的下一个节点指向新的链表的最前端 右边的那条链子
  269. cur.next = reverseHead.next;
  270. //头插法,将每一个遍历的节点插入到链表的最前端 左边的那条链子
  271. reverseHead.next = cur;
  272. //向后移动一位
  273. cur = next;
  274. }
  275. //将head.next 执行reverserHead.next 实现单链表的反转
  276. head.next = reverseHead.next;
  277. }
  278. //从头到尾打印单链表,使用栈数据结构
  279. public static void reversePrint(HeroNode head){
  280. //先判断 链表是否为空。
  281. if(head.next == null){
  282. return;
  283. }
  284. Stack<HeroNode> stack = new Stack<>();
  285. HeroNode temp = head.next;
  286. while (temp != null){
  287. //把节点压入栈中【先进后出】
  288. stack.push(temp);
  289. //向后移动一位
  290. temp = temp.next;
  291. }
  292. //将栈中的节点进行打印,因为是先进后出,所以就完成了倒序打印
  293. while (stack.size() > 0){
  294. System.out.println(stack.pop());
  295. }
  296. }
  297. }
  298. //创建一个节点类
  299. class HeroNode{
  300. public int id;
  301. public String name;
  302. public String nickname;
  303. public HeroNode next; //表示单向链表中的next区域,存放下一个节点
  304. //构造器,
  305. public HeroNode(int id, String name, String nickname) {
  306. this.id = id;
  307. this.name = name;
  308. this.nickname = nickname;
  309. }
  310. //重写toString的时候 next不需要重写
  311. @Override
  312. public String toString() {
  313. return "HeroNode{" +
  314. "id=" + id +
  315. ", name='" + name + '\'' +
  316. ", nickname='" + nickname + '\'' +
  317. '}';
  318. }
  319. }

三、单项链表面试题

1、求单项链表中有效节点的个数

(如果是带头节点的,不统计头节点)

  1. //用来查看链表中有效数据的个数
  2. //传过来一个头节点,即可得到他的长度
  3. public static int getLength(HeroNode head){
  4. //判断如果这个链表是空链表的话 直接return 0 就行
  5. if (head.next == null){
  6. return 0;
  7. }
  8. //因为不算上 头节点,所以是 head.next
  9. HeroNode temp = head.next;
  10. int length = 0;
  11. while ( temp != null){
  12. length ++;
  13. temp = temp.next;
  14. }
  15. return length;
  16. }
  17. 调用:
  18. System.out.println(HeadLinkedList.getLength(headLinkedList.getHead()));

2、查找单链表中的单数第K个节点【新浪面试题】

2.1 思路:

  1. 编写一个方法,接收 head节点,同时接收一个index
  2. index 是表示倒数第几个节点
  3. 先把链表从头到尾遍历一遍,得到长度
  4. 得到长度之后,我们从链表的第一个开始遍历(size - index)个,就可以得到了

    2.2 代码实现

    1. //用来查看链表中倒数第K个节点
    2. public static HeroNode getLast(HeroNode head, int index){
    3. //1.首先根据头节点 得到链表的长度
    4. //判断如果这个链表是空链表的话 直接return 0 就行
    5. if (head.next == null){
    6. return null;
    7. }
    8. //因为不算上 头节点,所以是 head.next
    9. HeroNode temp = head.next;
    10. int length = 0;
    11. while ( temp != null){
    12. length ++;
    13. temp = temp.next;
    14. }
    15. //2. 做一个数据的校验,判断index是否合理
    16. if(index <= 0 || index > length){
    17. return null;
    18. }
    19. //3. 进行遍历,倒数第几个 : 长度 - 倒数第几个 = 要找的那个
    20. HeroNode temp2 = head; //辅助变量,帮我们遍历
    21. for(int i = 0; i <= length - index; i++){
    22. if (temp2.next == null){
    23. break;
    24. }
    25. temp2 = temp2.next;
    26. }
    27. return temp2;
    28. }

    3、单链表的反转【腾讯面试题】

    3.1 思路:

  5. 先定义一个新的节点,HeroNode reverseHead = new HeroNode();

  6. 从头到尾遍历节点,将每一个遍历的节点放到头的后面 【头插法】
    • 这样就是 1 2 3 先插入1, 然后 在往1之前插入2 在往2 之前插入3
  7. 原来链表head.next = reverseHead.next;

    3.2 代码实现【头插法】

    1. //传入一个头节点,对其进行链表反转
    2. public static void reverse(HeroNode head){
    3. //如果链表为空 或者 链表中就一个元素,那么直接返回这个链表就行,不用反转
    4. if (head.next == null || head.next.next == null){
    5. return;
    6. }
    7. //定义一个辅助变量,帮助我们遍历链表
    8. HeroNode cur = head.next;
    9. //此变量永远指向 cur的下一个节点
    10. HeroNode next = null;
    11. //新的头节点
    12. HeroNode reverseHead = new HeroNode(0,"","");
    13. while (cur != null){
    14. //保存cur的下一个节点,防止丢失
    15. next = cur.next;
    16. //将cur的下一个节点指向新的链表的最前端 右边的那条链子
    17. cur.next = reverseHead.next;
    18. //头插法,将每一个遍历的节点插入到链表的最前端 左边的那条链子
    19. reverseHead.next = cur;
    20. //向后移动一位
    21. cur = next;
    22. }
    23. //将head.next 执行reverserHead.next 实现单链表的反转
    24. head.next = reverseHead.next;
    25. }

    4、从尾到头打印单链表【百度面试题,要求:1.反向遍历,2。Stack栈】

    4.1 思路

  • 思路1:将单链表反转过来,然后再打印【问题:会破坏原来链表的结构】
  • 思路2:利用栈数据结构,将各个节点压入栈中,利用栈的先进后出的特点 就实现了逆序打印的效果

    4.2 代码实现:

    1. //从头到尾打印单链表,使用栈数据结构
    2. public static void reversePrint(HeroNode head){
    3. //先判断 链表是否为空。
    4. if(head.next == null){
    5. return;
    6. }
    7. Stack<HeroNode> stack = new Stack<>();
    8. HeroNode temp = head.next;
    9. while (temp != null){
    10. //把节点压入栈中【先进后出】
    11. stack.push(temp);
    12. //向后移动一位
    13. temp = temp.next;
    14. }
    15. //将栈中的节点进行打印,因为是先进后出,所以就完成了倒序打印
    16. while (stack.size() > 0){
    17. System.out.println(stack.pop());
    18. }
    19. }

    5、合并两个有序的单链表,合并之后的链表依然有序

四、双向链表

1、双向链表和单向链表有什么不一样的地方

  • 除了有data域,next域,还有一个pre域 用来保存前一个节点。
  • head头部也有pre域,可以向头部的前面加节点

    2、分析双向链表的遍历,增加,删除,修改。

    2.1遍历思路:

  • 遍历跟单链表一样,只是可以向前,也可以向后查找

    2.2 添加思路:(添加到链表的最后)

  • 先找到双向链表的最后那个节点

  • 先让 **temp.next = 新节点 **
  • 然后 **新节点.pre = temp**

    2.3 修改思路:

  • 和原来的单项链表一致,先找到节点再修改

    2.4 删除思路:

  • 因为是双向链表,所以不需要找到 删除的前一个节点,直接找到要删除的节点就行

  • 然后 **要删除的节点.pre.next = 要删除的节点.next;**
  • 再然后 **要删除的节点.next.pre = 要删除的节点.pre**
    • 需要注意的是如果是删除最后一个节点的话,就不需要最后一句了,所以加一个if判断

未命名图片.png

五、双向链表代码实现

  1. package com.yixuexi.linkedList;
  2. /**
  3. * 2020/10/11 20:57
  4. */
  5. public class DoubleLinkedListDemo {
  6. public static void main(String[] args) {
  7. //进行测试
  8. //首先创建几个人物数据
  9. DoubleHeroNode n1 = new DoubleHeroNode(1,"松江","及时雨");
  10. DoubleHeroNode n2 = new DoubleHeroNode(2,"李逵","黑旋风");
  11. DoubleHeroNode n3 = new DoubleHeroNode(3,"吴用","智多星");
  12. DoubleHeroNode n4 = new DoubleHeroNode(4,"卢俊义","玉麒麟");
  13. DoubleHeroNode n5 = new DoubleHeroNode(5,"武大郎","烧饼哥");
  14. //创建链表
  15. DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
  16. doubleLinkedList.add(n1);
  17. doubleLinkedList.add(n2);
  18. doubleLinkedList.add(n3);
  19. doubleLinkedList.add(n4);
  20. doubleLinkedList.add(n5);
  21. //查看链表中的数据
  22. doubleLinkedList.list();
  23. //删除表中的数据
  24. doubleLinkedList.delete(1);
  25. doubleLinkedList.delete(5);
  26. System.out.println("-------------------------------->");
  27. //查看链表中的数据
  28. doubleLinkedList.list();
  29. DoubleHeroNode n6 = new DoubleHeroNode(5,"潘金莲","烧饼哥老婆");
  30. doubleLinkedList.update(n6);
  31. System.out.println("-------------------------------->");
  32. //查看链表中的数据
  33. doubleLinkedList.list();
  34. }
  35. }
  36. //
  37. class DoubleLinkedList{
  38. //在这个单项链表类中有一个 头节点,这个头节点不能动
  39. private DoubleHeroNode head = new DoubleHeroNode(0, "", "");
  40. public DoubleHeroNode getHead() {
  41. return head;
  42. }
  43. //遍历的方法
  44. //用来展示链表中的数据
  45. public void list() {
  46. //使用一个temp辅助变量来帮助我们进行循环遍历
  47. DoubleHeroNode temp = head;
  48. //首先判断一下这个链表是否为空链表
  49. //如果头节点的next域 为空则表示没有下一个节点,为空链表
  50. if (temp.next == null) {
  51. return;
  52. }
  53. // 因为上面做了判断了,所以直接输出就行
  54. //然后把temp往后移动一位,如果temp.next为空,则表示到头了
  55. while (true) {
  56. System.out.println(temp.next);
  57. temp = temp.next;
  58. if (temp.next == null) {
  59. break;
  60. }
  61. }
  62. }
  63. //用来添加节点的方法【从链表的尾部添加】
  64. public void add(DoubleHeroNode doubleHeroNode) {
  65. //由于每次添加节点都是往链表的最后去添加,所以先要遍历一遍链表,找出最后那个元素
  66. //这里遍历我们使用一个 temp临时变量 辅助我们遍历
  67. //这里这个temp就是头节点,使用循环,如果temp.next == null,则表示它是最后一个节点
  68. //如果 temp.next != null 则表示它不是最后一个节点,则往后移一位, temp = temp.next;
  69. DoubleHeroNode temp = head;
  70. while (true) {
  71. if (temp.next == null) {
  72. //说明此时 temp是最后一个节点 直接跳出循环
  73. break;
  74. }
  75. //能执行到这里,则表示不是最后一个节点,往后移动一位,继续循环
  76. temp = temp.next;
  77. }
  78. //执行到这里,说明此时的temp一定为最后一个节点,直接将节点添加到 temp.next 然后把添加的元素.pre = temp
  79. temp.next = doubleHeroNode;
  80. doubleHeroNode.pre = temp;
  81. }
  82. //删除元素的方法
  83. public void delete(int index){
  84. DoubleHeroNode temp = head;
  85. boolean flag = false;
  86. while (true){
  87. if (temp == null){
  88. return;
  89. }
  90. if (temp.id == index){
  91. flag = true;
  92. break;
  93. }
  94. temp = temp.next;
  95. }
  96. if (flag){
  97. temp.pre.next = temp.next;
  98. //这里代码有问题,如果是删除的最后一个节点的话会报出空指针异常,所以需要加上if判断
  99. if (temp.next != null){
  100. temp.next.pre = temp.pre;
  101. }
  102. }else{
  103. System.out.println("没有找到你要删除的节点");
  104. }
  105. }
  106. public void update(DoubleHeroNode doubleHeroNode){
  107. //需要使用一个辅助变量 temp
  108. DoubleHeroNode temp = head;
  109. //首先判断是否为空,如果为空直接退出
  110. if (temp.next == null) {
  111. System.out.println("链表为空,请加入节点后在进行更新!");
  112. return;
  113. }
  114. //使用循环找出id为 newHeroNode.id 的节点
  115. Boolean flag = false;
  116. while (true) {
  117. if (temp == null) {
  118. break;
  119. }
  120. //如果找到了 那么直接跳出循环,因为当前temp 就是需要修改的节点
  121. if (temp.id == doubleHeroNode.id) {
  122. flag = true;
  123. break;
  124. }
  125. //没有找到则向后移动一位
  126. temp = temp.next;
  127. }
  128. //判断 如果flag为true 则表示当前temp是需要修改的
  129. if (flag) {
  130. temp.name = doubleHeroNode.name;
  131. temp.nickname = doubleHeroNode.nickname;
  132. } else {
  133. System.out.println("没有找到对应id的节点,请输入正确的数据");
  134. }
  135. }
  136. }
  137. //创建一个有着双向节点的类
  138. class DoubleHeroNode {
  139. public int id;
  140. public String name;
  141. public String nickname;
  142. public DoubleHeroNode next; //表示双向链表中的next区域,存放下一个节点
  143. public DoubleHeroNode pre; //表示双向链表中的pre区域,存放上一个节点
  144. //构造器,
  145. public DoubleHeroNode(int id, String name, String nickname) {
  146. this.id = id;
  147. this.name = name;
  148. this.nickname = nickname;
  149. }
  150. @Override
  151. public String toString() {
  152. return "DoubleHeroNode{" +
  153. "id=" + id +
  154. ", name='" + name + '\'' +
  155. ", nickname='" + nickname + '\'' +
  156. '}';
  157. }
  158. }

六、单向环形链表

1、单向环形链表应用场景

约瑟夫问题:
Josephu 问题为:设编号为 1,2,… n 的 n 个人围坐一圈,约定编号为 k(1<=k<=n)的人从 1 开始报数,数到 m 的那个人出列,它的下一位又从 1 开始报数,数到 m 的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

假设:
n = 5 表示有几个人
k = 1 表示从第1个人 开始报数
m = 2 表示数两下【自身是1下,第二下那个人出列】然后第三个人数
最后出队列的顺序是 2->4->1->5->3
解决方案:使用单向环形链表,没有头节点,最后一个节点指向第一个节点

2、创建环形链表的思路

  • 先创建第一个节点,让first节点指向该节点,这样就形成了环状
  • 后面当我们没创建一个新的节点,就把该节点加入到以后的环形链表中即可。

    3、遍历环形链表的思路

  • 让一个辅助变量 curboy,指向first节点,

  • 然后通过一个while循环进行遍历
  • 再循环中 如果curboy.next = first 则可以break了,因为 前遍历的下一个节点是 第一个节点 则表示遍历完了

    未命名图片.png

    4、约瑟夫问题思路

    未命名图片.png

    约瑟夫问题代码实现

    ```java package com.yixuexi.linkedList;

/**

  • 2020/10/12 19:20 / /解决约瑟夫问题,模拟环形链表*/ public class Josepfu { public static void main(String[] args) {
    1. CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
    2. circleSingleLinkedList.addBoy(5);
    3. circleSingleLinkedList.showBoy();
    4. circleSingleLinkedList.countBoy(1,2,5);
    }

}

class CircleSingleLinkedList{ //创建一个 first 指针,这个指针永远指向 第一个节点。 public Boy first = null;

  1. //创建环形链表
  2. public void addBoy(int nums){
  3. //首先判断传进来的值合不合法,如果说构建一个小于1 个环形链表 那完全没必要
  4. if (nums < 1){
  5. System.out.println("构建链表节点数量太少,没必要!");
  6. return;
  7. }
  8. //创建一个辅助指针,帮助我们创建环形链表
  9. Boy curBoy = null;
  10. //进行构建
  11. for (int i = 1; i <= nums; i ++){
  12. //每循环一次,创建一个小孩
  13. Boy boy = new Boy(i);
  14. //如果是创建第一个小孩,那么这时第一个节点,让first指向这个第一个节点
  15. if ( i == 1 ){
  16. first = boy;
  17. //再将first的下一个节点指向boy,形成一个环
  18. first.setNext(boy);
  19. //让辅助节点指向first
  20. curBoy = first;
  21. }else{
  22. //倒数第二个节点的next 指向倒数第一个节点
  23. curBoy.setNext(boy);
  24. //将最后一个节点指向第一个节点,形成一个环
  25. boy.setNext(first);
  26. //向后一位移动
  27. curBoy = boy;
  28. }
  29. }
  30. }
  31. //遍历当前的单项环形链表
  32. public void showBoy(){
  33. if (first == null){
  34. System.out.println("空链表,没有必要做遍历");
  35. return;
  36. }
  37. Boy curBoy = first;
  38. while (true){
  39. System.out.println("小孩的编号为:" + curBoy.getNo());
  40. //因为是环形链表,所以说 如果当前遍历的下一个节点是 第一个节点 则表示遍历完了
  41. if (curBoy.getNext() == first){
  42. break;
  43. }
  44. //向后移动一位
  45. curBoy = curBoy.getNext();
  46. }
  47. }
  48. // 根据用户的输入,计算出小孩出圈的顺序
  49. /**
  50. *
  51. * @param starNo 表示从第几个小孩开始数
  52. * @param countNum 表示数几下
  53. * @param nums 表示最初有几个小孩在圈中
  54. */
  55. public void countBoy(int starNo,int countNum, int nums){
  56. //先对数据进行一波校验
  57. if (first == null || starNo < 1 || starNo > nums){
  58. System.out.println("输入的参数有误,请重新输入");
  59. return;
  60. }
  61. //创建辅助节点,帮助完成小孩出圈
  62. Boy helper = first;
  63. //将helper 移动到这个链表的最后一个节点
  64. while (true){
  65. if (helper.getNext() == first){
  66. break;
  67. }
  68. //向后移动一位
  69. helper = helper.getNext();
  70. }
  71. //在小孩报数前,先让first 和 helper移动 k-1 次
  72. for (int j = 0; j < starNo - 1; j ++){
  73. first = first.getNext();
  74. helper = helper.getNext();
  75. }
  76. //当小孩报数时,让first和helper指针同时移动 m -1次,然后出圈
  77. //通过循环,让这个链表中 到最后只剩下一个节点
  78. while (true){
  79. if (helper == first){
  80. break;
  81. }
  82. //让first和helper同时的移动 countNum - 1
  83. for (int i = 0; i < countNum -1 ; i++){
  84. first = first.getNext();
  85. helper = helper.getNext();
  86. }
  87. //此时的first 指向的就是要出圈小孩的节点,
  88. System.out.println(first.getNo());
  89. //将小孩出圈
  90. first = first.getNext();
  91. helper.setNext(first);
  92. }
  93. System.out.println("最后留在圈中的小孩的编号为 " + first.getNo());
  94. }

}

//小孩节点 class Boy{ private int no; //小孩id private Boy next;

  1. public Boy(int no) {
  2. this.no = no;
  3. }
  4. public int getNo() {
  5. return no;
  6. }
  7. public void setNo(int no) {
  8. this.no = no;
  9. }
  10. public Boy getNext() {
  11. return next;
  12. }
  13. public void setNext(Boy next) {
  14. this.next = next;
  15. }

}

```