一、单向链表

1、链表是有序结构

(1)带头单链表内存中的存储:

image.png 1、链表是以节点的方式来存储,是链式存储
2、每个节点包含 data 域, next 域:指向下一个节点.
3、如图:发现链表的各个节点不一定是连续存储.
4、链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定

(2)带头单链表逻辑结构示意图:

image.png 1、逻辑上看就是,根据头连续,但是内存中的排布则未必连续存储
2、就像你的爷爷,父亲,你(三代人,是连续的),但是你们三人家族在这个世界
不同的角落,身边出现的可能是其他家族的人,他们也是这样的连续子代关系
3、宗族看逻辑图,世界看内存图(世界是广度现实)

(3)小结:

对这两幅图,我想了一个口令:逻辑有序,内存无序

2、单链表:直接插入(按插入顺序插入),无考虑到排行

(1)思路:

image.png 一、添加(创建)
1、先创建一个head 头节点, 作用就是表示单链表的头
2、后面我们每添加一个节点,就直接加入到 链表的最后
二、遍历:
1、通过一个辅助变量遍历,帮助遍历整个链表,为null 终止


(2)代码:

  1. package com.kk.datastructure.linkedlist;
  2. public class SingleLinkedListDemo {
  3. public static void main(String[] args) {
  4. //先创建节点
  5. HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
  6. HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
  7. HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
  8. HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
  9. //创建要给链表
  10. SingleLinkedList singleLinkedList = new SingleLinkedList();
  11. //加入
  12. singleLinkedList.add(hero1);
  13. singleLinkedList.add(hero4);
  14. singleLinkedList.add(hero2);
  15. singleLinkedList.add(hero3);
  16. singleLinkedList.list();
  17. }
  18. }
  19. //定义HeroNode , 每个HeroNode 对象就是一个节点
  20. class HeroNode {
  21. public int no; // 好汉排行
  22. public String name; // 好汉姓名
  23. public String nickname;// 好汉外号
  24. public HeroNode next; //指向下一个节点(好汉)
  25. //构造器
  26. public HeroNode(int no, String name, String nickname) {
  27. this.no = no;
  28. this.name = name;
  29. this.nickname = nickname;
  30. }
  31. //为了显示方法,我们重新toString
  32. @Override
  33. public String toString() {
  34. return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
  35. }
  36. }
  37. // 定义 SingleLinkedList 管理梁山英雄
  38. class SingleLinkedList{
  39. // 先初始化一个头节点,头节点不能动,不存放具体的数据(可以理解为王伦 -- 初代寨主,后被林冲捅了)
  40. private HeroNode head = new HeroNode(0, null, null);
  41. // 返回头结点
  42. public HeroNode getHead() {
  43. return head;
  44. }
  45. // 添加节点到单向链表
  46. // 思路,当不考虑编号顺序时
  47. // 1. 找到当前链表的最后节点
  48. // 2. 将最后这个节点的next 指向 新的节点
  49. public void add(HeroNode heroNode) {
  50. // 因为head节点不能动,因此我们需要一个辅助变量 temp(可以理解为晁天王)
  51. HeroNode temp = head;
  52. // 遍历链表,找到最后
  53. while(true) {
  54. // 找到链表的最后是null
  55. if(temp.next == null)break;
  56. // 若没有找到最后,则后移(这里是n+1次的,n个有效数据)
  57. temp = temp.next;
  58. }
  59. // 当退出while循环时,temp就指向了链表的最后
  60. // 将最后这个节点的next 指向 新的节点
  61. temp.next = heroNode;
  62. }
  63. // 遍历链表
  64. public void list() {
  65. // 判断链表是否为空
  66. if (head.next == null) {
  67. System.out.println("链表为空~~~");
  68. return;
  69. }
  70. // 因为头节点,不能动,因此我们需要一个辅助变量来遍历
  71. HeroNode temp = head.next;
  72. while (true) {
  73. // 判断是否到链表的最后
  74. if (temp == null)
  75. return;
  76. // 输出节点信息
  77. System.out.println(temp);
  78. // 将节点后移,注意要加!!
  79. temp = temp.next;
  80. }
  81. }
  82. }


(3)缺点:

1、无法按照英雄编号排序,只能按照插入顺序。
2、并且重复了同排行序号的英雄,也随意可以添加,这是在梁山不被允许的。

3、单链表:按顺序插入,删除,修改,重复排行不得添加

(1)添加<按照编号顺序>的思路:

3、链表 - 图4 1、首先找到新添加的节点的位置, 是通过辅助变量(指针), 通过遍历来搞定
2、新的节点 a,a.next = temp.next
3、将temp.next = a ,新的节点

(2)修改:

编号不变,姓名和外号可变

(3)删除节点的思路

3、链表 - 图5 1、我们先找到 需要删除的这个节点的前一个节点 temp
2、temp.next = temp.next.next
3、被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收

(4)以上增、改、删的代码:

  1. package com.kk.datastructure.linkedlist.improve;
  2. public class SingleLinkedListDemo {
  3. public static void main(String[] args) {
  4. //先创建节点
  5. HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
  6. HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
  7. HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
  8. HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
  9. //创建要给链表
  10. SingleLinkedList singleLinkedList = new SingleLinkedList();
  11. singleLinkedList.addByOrder(hero1);
  12. singleLinkedList.addByOrder(hero4);
  13. singleLinkedList.addByOrder(hero2);
  14. singleLinkedList.addByOrder(hero3);
  15. // singleLinkedList.addByOrder(hero3);
  16. // singleLinkedList.list();
  17. // 修改
  18. // singleLinkedList.update(new HeroNode(1, "送浆", "白日"));
  19. // singleLinkedList.list();
  20. // 删除
  21. singleLinkedList.delete(1);
  22. singleLinkedList.delete(2);
  23. singleLinkedList.delete(3);
  24. singleLinkedList.delete(4);
  25. singleLinkedList.list();
  26. }
  27. }
  28. //定义HeroNode , 每个HeroNode 对象就是一个节点
  29. class HeroNode {
  30. public int no; // 好汉排行
  31. public String name; // 好汉姓名
  32. public String nickname;// 好汉外号
  33. public HeroNode next; //指向下一个节点(好汉)
  34. //构造器
  35. public HeroNode(int no, String name, String nickname) {
  36. this.no = no;
  37. this.name = name;
  38. this.nickname = nickname;
  39. }
  40. //为了显示方法,我们重新toString
  41. @Override
  42. public String toString() {
  43. return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
  44. }
  45. }
  46. // 定义 SingleLinkedList 管理梁山英雄
  47. class SingleLinkedList{
  48. // 先初始化一个头节点,头节点不能动,不存放具体的数据(可以理解为王伦 -- 初代寨主,后被林冲捅了)
  49. private HeroNode head = new HeroNode(0, null, null);
  50. // 返回头结点
  51. public HeroNode getHead() {
  52. return head;
  53. }
  54. // 删除
  55. //1. head 不能动,因此我们需要一个temp辅助节点找到待删除节点的前一个节点
  56. //2. 说明我们在比较时,是temp.next.no 和 需要删除的节点的no比较
  57. public void delete(int no) {
  58. HeroNode temp = head;
  59. boolean flag = false;// 标志是否找到待删除节点
  60. while(true) {
  61. if(temp.next ==null)
  62. // 已经到链表的最后
  63. break;
  64. if(temp.next.no == no) {
  65. // 已经找到
  66. flag = true;
  67. break;
  68. }
  69. // 移动继续找
  70. temp = temp.next;
  71. }
  72. // 通过 flag 进行判断
  73. if(flag) {
  74. // 已经找到,指向后一个引用
  75. temp.next = temp.next.next;
  76. }else
  77. System.out.printf("没有找到排行为【%d】要修改的英雄",no);
  78. }
  79. // 更新节点的信息, 根据no编号来修改,即no编号不能改
  80. public void update(HeroNode heroNode) {
  81. // 判空
  82. if(head.next == null) {
  83. System.out.println("链表为空~~");
  84. return;
  85. }
  86. // 根据 no 找到需要修改的节点
  87. // 定义一个辅助变量
  88. HeroNode temp = head.next;
  89. boolean flag = false;// 表示是否找到节点
  90. while (true) {
  91. if (temp == null)
  92. // 链表已经遍历完
  93. break;
  94. if(temp.no == heroNode.no) {
  95. // 节点已经找到
  96. flag = true;
  97. break;
  98. }
  99. // 节点移动,继续找
  100. temp = temp.next;
  101. }
  102. // 根据 flag 判断
  103. if(flag) {
  104. // 找到,修改姓名和外号
  105. temp.name = heroNode.name;
  106. temp.nickname = heroNode.nickname;
  107. }else {
  108. System.out.printf("没有找到排行为【%d】要修改的英雄",heroNode.no);
  109. }
  110. }
  111. // 按编号顺序添加(如果有这个排名,则添加失败,并给出提示)
  112. public void addByOrder(HeroNode heroNode) {
  113. // 因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
  114. // 因为单链表,因为我们找的temp 是位于 添加位置的前一个节点,否则插入不了
  115. HeroNode temp = head;
  116. boolean flag = false;// flag标志添加的编号是否存在,默认为false
  117. while(true) {
  118. if(temp.next == null) break;
  119. if(temp.next.no > heroNode.no)
  120. // 位置找到,就在 temp后面
  121. // 因为 它满足了 按顺序 ,所以可以插入
  122. break;
  123. if(temp.next.no == heroNode.no) {
  124. // 已经存在改排行的编号(不可重复)
  125. flag = true;
  126. break;
  127. }
  128. // 没满足以上,后移下一次节点继续找
  129. temp = temp.next;
  130. }
  131. // 对 flag 进行判断
  132. if(flag)
  133. // 不能添加,说明编号已经存在
  134. System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入\n", heroNode.no);
  135. else {
  136. // 插入到链表中,temp的后面
  137. heroNode.next = temp.next;// 我后面的是6号,现在你后面是6号
  138. temp.next = heroNode;// 我后面是你
  139. }
  140. }
  141. // 遍历链表
  142. public void list() {
  143. // 判断链表是否为空
  144. if (head.next == null) {
  145. System.out.println("链表为空~~~");
  146. return;
  147. }
  148. // 因为头节点,不能动,因此我们需要一个辅助变量来遍历
  149. HeroNode temp = head.next;
  150. while (true) {
  151. // 判断是否到链表的最后
  152. if (temp == null)
  153. return;
  154. // 输出节点信息
  155. System.out.println(temp);
  156. // 将节点后移,注意要加!!
  157. temp = temp.next;
  158. }
  159. }
  160. }

4、单向链表经典面试题(新浪,百度,腾讯) —- 5题

1、题目以及思路:

1、求单链表中有效节点的个数
(1)遍历节点后的个数即可
2、查找单链表中的倒数第k个结点 【新浪面试题】
(1) 有效的总长度 - 要查询的位置 = 倒数的位置 【8-2=6】

3、单链表的反转【腾讯面试题,有点难度】image.pngimage.png (1)先定义一个节点 reverseHead = new HeroNode()
(2)从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead 的最前端
(3)原来的链表的head.next = reverseHead.next

4、从尾到头打印单链表 【百度,要求方式1:反向遍历 。 方式2:Stack栈】
(1) 方式1: 先将单链表进行反转操作,然后再遍历即可,这样的做的问题是会破坏原来的单链表的结构,不建议
(2) 方式2:可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果. image.png 5、合并两个有序的单链表,合并之后的链表依然有序
(1)创建一个新链表,然后比较两个链表谁排行高,则先存入新链表中 image.png

1-2-3-4-5 五题的代码:

  1. package com.datastructure.linkedlist.interview;
  2. import java.util.Stack;
  3. public class SingleLinkedListInterview {
  4. /**
  5. * @param args
  6. */
  7. public static void main(String[] args) {
  8. // 先创建节点
  9. HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
  10. HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
  11. HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
  12. HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
  13. HeroNode hero5 = new HeroNode(5, "鲁智深", "花和尚");
  14. HeroNode hero6 = new HeroNode(6, "武松", "行者");
  15. // 创建要给链表
  16. SingleLinkedList singleLinkedList = new SingleLinkedList();
  17. // singleLinkedList.addByOrder(hero1);
  18. // singleLinkedList.addByOrder(hero4);
  19. // singleLinkedList.addByOrder(hero2);
  20. // singleLinkedList.addByOrder(hero3);
  21. singleLinkedList.list();
  22. // 有效个数
  23. int length = singleLinkedList.getLength(singleLinkedList.getHead());
  24. System.out.printf("有效个数为:%d\n",length);
  25. // 倒数第 K 的节点
  26. HeroNode resultNode = singleLinkedList.findLastIndexNode(singleLinkedList.getHead(), 3);
  27. System.out.println(resultNode);
  28. // 链表反转
  29. singleLinkedList.reversetList(singleLinkedList.getHead());
  30. singleLinkedList.list();
  31. // 利用栈反向打印
  32. singleLinkedList.reversetPrint(singleLinkedList.getHead());
  33. System.out.println("--------------------美丽的分割线---------------------------");
  34. SingleLinkedList singleLinkedList1 = new SingleLinkedList();
  35. SingleLinkedList singleLinkedList2 = new SingleLinkedList();
  36. singleLinkedList1.add(hero6);
  37. singleLinkedList1.add(hero2);
  38. singleLinkedList1.add(hero4);
  39. singleLinkedList2.add(hero5);
  40. singleLinkedList2.add(hero1);
  41. singleLinkedList2.add(hero3);
  42. // singleLinkedList1.list();
  43. // singleLinkedList2.list();
  44. System.out.println("--------------------美丽的分割线---------------------------");
  45. HeroNode newHead = singleLinkedList.sumLinkedList(singleLinkedList1.getHead(),
  46. singleLinkedList2.getHead());
  47. SingleLinkedList singleLinkedList3 = new SingleLinkedList();
  48. singleLinkedList3.setHead(newHead);
  49. singleLinkedList3.list();
  50. }
  51. }
  52. //定义 SingleLinkedList 管理梁山英雄
  53. class SingleLinkedList {
  54. // 先初始化一个头节点,头节点不能动,不存放具体的数据(可以理解为王伦 -- 初代寨主,后被林冲捅了)
  55. private HeroNode head = new HeroNode(0, null, null);
  56. // 返回头结点
  57. public HeroNode getHead() {
  58. return head;
  59. }
  60. public void setHead(HeroNode head) {
  61. this.head = head;
  62. }
  63. /**
  64. * 题目五:合并两个有序的单链表,合并之后的链表依然有序
  65. * @param head1
  66. * @param head2
  67. * 想了很久,有两个解法:只实现方案一
  68. * 方案一:遍历A 链表找出最小的号,再遍历B链表找出最小,然后比较
  69. * 小的先插入到新链表中,依次重复(每一次最小的,要被摘除)--有问题未解决
  70. * 方案二:就是模仿数组的冒泡排序,左右比较交换,未实现
  71. * @return
  72. */
  73. public static HeroNode sumLinkedList(HeroNode head1,HeroNode head2) {
  74. // 判空
  75. if(head1.next ==null || head2.next == null)return null;
  76. // 定义一个新链表 用于存储合并
  77. //SingleLinkedList singleLinkedList = new SingleLinkedList();
  78. // 定义一个新链表的头
  79. HeroNode newHead = new HeroNode(0, "", "");
  80. // 先合并再排序
  81. // 合并 head1 链表最后一个接 head2的后米面一个
  82. HeroNode cuc1 = head1.next;
  83. HeroNode temp = null;
  84. while(cuc1!=null) {
  85. // 最后一个为null 将他前一个先保存
  86. temp = cuc1;
  87. cuc1 = cuc1.next;
  88. }
  89. // head2 绑定head1 的后面一个
  90. temp.next = head2.next;
  91. // 现在已经输合并的无序链表 head1
  92. // 遍历得到最小
  93. HeroNode minNode = null;
  94. // 最小的前一个,用于摘除原始最小
  95. HeroNode minNodeFront = null;
  96. // 辅助遍历最小
  97. HeroNode minTemp1 = head1.next;
  98. // 填充 次数为 链表长度
  99. int lengt1 = SingleLinkedList.getLength(newHead);
  100. int lengt2 = SingleLinkedList.getLength(head1);
  101. while(SingleLinkedList.getLength(newHead)<=lengt2)
  102. {
  103. while(minTemp1.next != null) {
  104. minNode = minTemp1;
  105. // 临时保存 用于摘除它后面的
  106. //HeroNode minTemp2 = minTemp1;
  107. if(minNode.no > minNode.next.no) {
  108. // 赋值
  109. minNodeFront = minTemp1;
  110. minNode = minNode.next;
  111. }
  112. // 后移
  113. minTemp1 = minTemp1.next;
  114. }
  115. // 摘除最小
  116. minNodeFront.next = minNodeFront.next.next;
  117. // 绑定到 新链表
  118. minNode.next = newHead.next;
  119. newHead.next = minNode;
  120. }
  121. return newHead;
  122. }
  123. /**
  124. * 题目四:反向打印链表(栈)
  125. * @param head
  126. */
  127. public static void reversetPrint(HeroNode head) {
  128. // 判空
  129. if(head.next == null)return;
  130. // 创建一个栈,用于节点出入栈,实现反向打印
  131. Stack<HeroNode> stack = new Stack();
  132. // 定义辅助指针,用于遍历原始链表
  133. HeroNode cuc = head.next;
  134. // 将链表所有节点入栈
  135. while (cuc != null) {
  136. stack.push(cuc);// 节点 入栈
  137. cuc = cuc.next;// 节点后移
  138. }
  139. // 所有节点依次出栈
  140. while( stack.size() >0) {
  141. //stack的特点是先进后出
  142. System.out.println(stack.pop());
  143. }
  144. }
  145. /**
  146. * 题目三:单链表的反转【腾讯面试题,有点难度】
  147. *
  148. * @param head
  149. */
  150. public static void reversetList(HeroNode head) {
  151. // 若链表为空,或者只有一个则不需要反转
  152. if (head.next == null || head.next.next == null) {
  153. return;
  154. }
  155. // 定义一个辅助指针,用户遍历原始未反转的那个链表
  156. HeroNode cuc = head.next;
  157. // 用于动态指向 当前节点【cuc】的 下一个节点
  158. HeroNode next = null;
  159. // 新链表(反转后的)的链表头
  160. HeroNode reversetHead = new HeroNode(0, "", "");
  161. // 思路: 遍历原始链表,没遍历一个节点将其取出,并放在新链表头(reversetHead)的后面的最前端
  162. // 每一个新节点,都会取代旧的,与新链表头牵手
  163. while (cuc != null) {
  164. // 暂时先保存当前节点的下一个节点,用于后移
  165. next = cuc.next;
  166. // 将要 遍历出来的新节点,指向 新链表头 后面的最前端(将情敌的手,从她手上拨开)
  167. cuc.next = reversetHead.next;
  168. // 将 cuc 连接到 新的链表头后面的最前端(她的手和你牵了)
  169. reversetHead.next = cuc;
  170. // cuc 后移,换下一个情敌来打你
  171. cuc = next;
  172. }
  173. // 将head.next 指向 reverseHead.next , 实现单链表的反转
  174. // 新链表(她)池塘养的鱼和旧链表头(她的闺蜜)共享
  175. head.next = reversetHead.next;
  176. }
  177. /**
  178. * 题目二:查找单链表中的倒数第k个结点 【新浪面试题】
  179. *
  180. * @param head 传入的头结点
  181. * @param HeroNode 需要查找的 倒数节点数据
  182. * @return
  183. */
  184. // 思路:
  185. // 1、先遍历得到 有效数据个数(总长度--不包括头节点)
  186. // 2、有效个数 - index = 倒数 K(size-index = k )
  187. // 3、找到则返回当前节点,找不到返回 null
  188. public static HeroNode findLastIndexNode(HeroNode head, int index) {
  189. // 判空
  190. if (head.next == null)
  191. return null;
  192. // 第一个遍历得到链表的长度(节点个数)
  193. int size = getLength(head);
  194. // 第二次遍历 size-index 位置,就是我们倒数的第K个节点
  195. // 先做一个index的校验
  196. if (index <= 0 || index > size) {
  197. // 查找的位置不能大过总长
  198. return null;
  199. }
  200. // 定义给辅助变量, for 循环定位到倒数的index
  201. HeroNode cuc = head.next;// 8-2=6,倒数第二个就是第六个
  202. for (int i = 0; i < size - index; i++) {
  203. cuc = cuc.next;
  204. }
  205. return cuc;
  206. }
  207. /**
  208. * 题目一:获取有效节点个数
  209. *
  210. * @param head 头结点(不统计)
  211. * @return 返回有效节点个数
  212. */
  213. public static int getLength(HeroNode head) {
  214. // 判空
  215. if (head.next == null)
  216. return 0;
  217. int length = 0;
  218. // 定义辅助变量 cuc,此处没有统计 头节点
  219. HeroNode cuc = head.next;
  220. while (cuc != null) {
  221. length++;
  222. cuc = cuc.next;// 指向下一个,继续遍历
  223. }
  224. return length;
  225. }
  226. // 按编号顺序添加(如果有这个排名,则添加失败,并给出提示)
  227. public void addByOrder(HeroNode heroNode) {
  228. // 因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
  229. // 因为单链表,因为我们找的temp 是位于 添加位置的前一个节点,否则插入不了
  230. HeroNode temp = head;
  231. boolean flag = false;// flag标志添加的编号是否存在,默认为false
  232. while (true) {
  233. if (temp.next == null)
  234. break;
  235. if (temp.next.no > heroNode.no)
  236. // 位置找到,就在 temp后面
  237. // 因为 它满足了 按顺序 ,所以可以插入
  238. break;
  239. if (temp.next.no == heroNode.no) {
  240. // 已经存在改排行的编号(不可重复)
  241. flag = true;
  242. break;
  243. }
  244. // 没满足以上,后移下一次节点继续找
  245. temp = temp.next;
  246. }
  247. // 对 flag 进行判断
  248. if (flag)
  249. // 不能添加,说明编号已经存在
  250. System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入\n", heroNode.no);
  251. else {
  252. // 插入到链表中,temp的后面
  253. heroNode.next = temp.next;// 我后面的是6号,然后我上前走过去,你走了,现在你后面是6号
  254. temp.next = heroNode;// 我后面是你,上下两句顺序不可对调
  255. }
  256. }
  257. public void add(HeroNode heroNode) {
  258. // 因为head节点不能动,因此我们需要一个辅助变量 temp(可以理解为晁天王)
  259. HeroNode temp = head;
  260. // 遍历链表,找到最后
  261. while(true) {
  262. // 找到链表的最后是null
  263. if(temp.next == null)break;
  264. // 若没有找到最后,则后移(这里是n+1次的,n个有效数据)
  265. temp = temp.next;
  266. }
  267. // 当退出while循环时,temp就指向了链表的最后
  268. // 将最后这个节点的next 指向 新的节点
  269. temp.next = heroNode;
  270. }
  271. // 遍历链表
  272. public void list() {
  273. // 判断链表是否为空
  274. if (head.next == null) {
  275. System.out.println("链表为空~~~");
  276. return;
  277. }
  278. // 因为头节点,不能动,因此我们需要一个辅助变量来遍历
  279. HeroNode temp = head.next;
  280. while (true) {
  281. // 判断是否到链表的最后
  282. if (temp == null)
  283. return;
  284. // 输出节点信息
  285. System.out.println(temp);
  286. // 将节点后移,注意要加!!
  287. temp = temp.next;
  288. }
  289. }
  290. }
  291. //定义HeroNode , 每个HeroNode 对象就是一个节点
  292. class HeroNode {
  293. public int no; // 好汉排行
  294. public String name; // 好汉姓名
  295. public String nickname;// 好汉外号
  296. public HeroNode next; // 指向下一个节点(好汉)
  297. // 构造器
  298. public HeroNode(int no, String name, String nickname) {
  299. this.no = no;
  300. this.name = name;
  301. this.nickname = nickname;
  302. }
  303. // 为了显示方法,我们重新toString
  304. @Override
  305. public String toString() {
  306. return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
  307. }
  308. }

二、双向链表

1、双向链表的操作分析和实现

管理单向链表的缺点分析:

1) 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
2) 单向链表不能自我删除,需要靠辅助节点 ,而双向链表,则可以自我删除,所以前面我们单链表删除 时节点,总是找到 temp,temp 是待删除节点的前一个节点(认真体会).
3) 分析了双向链表如何完成遍历,添加,修改和删除的思路

双向链表分析思路:

image.png
对上图的说明:
分析 双向链表的遍历,添加,修改,删除的操作思路===》代码实现 1) 遍历 方和 单链表一样,只是可以向前,也可以向后查找
2) 添加 (默认添加到双向链表的最后)
(1) 先找到双向链表的最后这个节点
(2) temp.next = newHeroNode
(3) newHeroNode.pre = temp;
3) 修改 思路和 原来的单向链表一样.
4) 删除
(1) 因为是双向链表,因此,我们可以实现自我删除某个节点
(2) 直接找到要删除的这个节点,比如 temp
(3) temp.pre.next = temp.next
(4) temp.next.pre = temp.pre;

代码实现:

  1. package com.atguigu.linkedlist;
  2. public class DoubleLinkedListDemo {
  3. public static void main(String[] args) {
  4. //测试一下
  5. //县创建节点
  6. HeroNode2 hero1 = new HeroNode2(1, "宋江", "及时雨");
  7. HeroNode2 hero2 = new HeroNode2(2, "卢俊义", "玉麒麟");
  8. HeroNode2 hero3 = new HeroNode2(3, "吴用", "智多星");
  9. HeroNode2 hero4 = new HeroNode2(4, "林冲", "豹子头");
  10. //创建一个双向链表
  11. DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
  12. //添加节点
  13. doubleLinkedList.add(hero1);
  14. doubleLinkedList.add(hero2);
  15. doubleLinkedList.add(hero3);
  16. doubleLinkedList.add(hero4);
  17. //输出链表
  18. doubleLinkedList.list();
  19. //修改链表
  20. HeroNode2 newHeroNode = new HeroNode2(2,"秦明","霹雳火");
  21. doubleLinkedList.update(newHeroNode);
  22. System.out.println("修改后的链表情况");
  23. //输出链表
  24. doubleLinkedList.list();
  25. //删除链表
  26. doubleLinkedList.del(3);
  27. System.out.println("删除后的链表");
  28. //输出链表
  29. doubleLinkedList.list();
  30. }
  31. }
  32. //创建一个双向链表的类
  33. class DoubleLinkedList{
  34. //先初始化一个头结点,头结点不要动
  35. private HeroNode2 head = new HeroNode2(0, "", "");
  36. //返回头结点
  37. public HeroNode2 getHead(){
  38. return head;
  39. }
  40. //遍历双向链表
  41. //显示链表,遍历
  42. public void list() {
  43. //判断链表是否为空
  44. if (head.next == null) {
  45. System.out.println("链表为空");
  46. return;
  47. }
  48. //因为head节点不能动,因此需要一个辅助变量temp遍历
  49. HeroNode2 temp = head.next;
  50. while (true) {
  51. //指向链表最后
  52. if (temp == null) {
  53. break;
  54. }
  55. //输出节点信息
  56. System.out.println(temp);
  57. //将temp后移,一定小心
  58. temp = temp.next;
  59. }
  60. }
  61. //添加对象到双向链表末尾
  62. public void add(HeroNode2 heroNode) {
  63. //因为head节点不能动,因此需要一个辅助变量temp遍历
  64. HeroNode2 temp = head;
  65. //比那里链表,找到最后
  66. while (true) {
  67. //指向链表最后
  68. if (temp.next == null) {
  69. break;
  70. }
  71. //如果没有找到最后,将temp后移
  72. temp = temp.next;
  73. }
  74. //当退出while循环的时候,temp就指向了链表的最后
  75. //形成一个双向链表
  76. temp.next = heroNode;
  77. heroNode.pre = temp;
  78. }
  79. //删除某个节点
  80. //1、对于双向链表,可以直接找到亚删除的节点
  81. //2、
  82. public void del(int no){
  83. //判断当前链表是否为空
  84. if(head.next == null){
  85. System.out.println("链表为空");
  86. return;
  87. }
  88. HeroNode2 temp =head.next;
  89. boolean flag = false;
  90. while(true){
  91. if(temp == null){//已到链表最后
  92. break;
  93. }
  94. if(temp.next.no == no){
  95. //找到待删除节点的前一个节点temp
  96. flag =true;
  97. break;
  98. }
  99. temp = temp.next;
  100. }
  101. //判断flag
  102. if(flag){
  103. //temp.next = temp.next.next; 单向链表
  104. temp.pre.next = temp.next;
  105. //如果这是最后一个节点,就不需要执行下面这句话,否则出现空指针异常。temp.next=null
  106. if(temp.next != null){
  107. temp.next.pre = temp.pre;
  108. }
  109. }else{
  110. System.out.printf("要删除的 %d 节点不存在\n",no);
  111. }
  112. }
  113. //修改链表节点,和单向链表一致
  114. public void update(HeroNode2 newHeroNode) {
  115. if (this.head.next == null) {
  116. System.out.println("链表为空~");
  117. } else {
  118. HeroNode2 temp = this.head.next;
  119. boolean flag;
  120. for(flag = false; temp != null; temp = temp.next) {
  121. if (temp.no == newHeroNode.no) {
  122. flag = true;
  123. break;
  124. }
  125. }
  126. if (flag) {
  127. temp.name = newHeroNode.name;
  128. temp.nickname = newHeroNode.nickname;
  129. } else {
  130. System.out.printf("没有找到 编号 %d 的节点,不能修改\n", newHeroNode.no);
  131. }
  132. }
  133. }
  134. }
  135. //定义HeroNode2对象,每个HeroNode对象就是一个节点
  136. class HeroNode2 {
  137. public int no;
  138. public String name;
  139. public String nickname;
  140. public HeroNode2 next; //指向下一个节点
  141. public HeroNode2 pre; //指向上一个节点
  142. //构造器
  143. public HeroNode2(int no, String name, String nickname) {
  144. this.no = no;
  145. this.name = name;
  146. this.nickname = nickname;
  147. }
  148. //为了显示方法,我们重新 toString @Override
  149. public String toString() {
  150. return "HeroNode{" +
  151. "no=" + no +
  152. ", name='" + name + '\'' +
  153. ", nickname='" + nickname + '\'' +
  154. '}';
  155. }
  156. }
  157. //作业:双向链表的第二种添加方式:参考单向链表添加方式
  158. //todo

三、单向环形链表

1、单向环形链表图解

image.png

2、Josephu问题

1、Josephu(约瑟夫、约瑟夫环) 问题

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

2、解决方法

用一个不带头结点的循环链表来处理Josephu问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束

3、待补充

  1. <br /> <br /> <br /> <br />