一、链表介绍

链表内存中的状态以及小结

QQ截图20200723110033.jpg
QQ截图20200723110040.jpg


二、单链表的应用实例

完整代码:
SingleLinkedListDemo.java

创建节点以及管理链表类

说明:需要创建两个类,一个是节点类,另一个是管理链表的类
节点类中需要id、name、sickname以及节点类(用于指向下一个节点)
管理链表中的类 首先创建一个头节点(里面没有数据,并且保持不变),包含方法有:添加方法(插入到链表尾部),遍历链表。

如下:

  1. //创建管理链表的类
  2. class SingleLinkedList{
  3. //先初始化一个头节点, 头节点不要动, 不存放具体的数据
  4. HeroNode head = new HeroNode(0, "", "");
  5. ...其他等方法
  6. }
  7. //创建节点
  8. class HeroNode{
  9. public int id;
  10. public String name;
  11. public String nickname;
  12. public HeroNode next;
  13. //创建一个构造器,存放id、name、nickname
  14. public HeroNode(int id, String name, String nickname) {
  15. super();
  16. this.id = id;
  17. this.name = name;
  18. this.nickname = nickname;
  19. }
  20. @Override
  21. public String toString() {
  22. return "HeroNode [id=" + id + ", name=" + name + ", nickname=" + nickname + "]";
  23. }
  24. }

第一种添加方式:添加到链表尾部(包含遍历)

说明:下面是添加节点到链表尾部的方法以及遍历链表的方法。

  1. package LinkedList;
  2. //创建管理链表的类
  3. class SingleLinkedList{
  4. //先初始化一个头节点, 头节点不要动, 不存放具体的数据
  5. HeroNode head = new HeroNode(0, "", "");
  6. //进行添加节点到末尾
  7. public void addNode(HeroNode node) {
  8. //首先使用一个临时变量代替head
  9. HeroNode temp = head;
  10. //寻找到链表的最底部
  11. while(true) {
  12. if(temp.next == null) {
  13. break;
  14. }
  15. temp = temp.next;
  16. }
  17. //插入node节点到最底部
  18. temp.next = node;
  19. }
  20. //遍历链表
  21. public void showList() {
  22. //判断链表是否为空
  23. if(head.next == null) {
  24. System.out.println("链表为空,无法进行遍历数据!");
  25. return;
  26. }
  27. //使用一个临时变量获取到链表下一个节点
  28. HeroNode temp = head.next;
  29. while(true) {
  30. if(temp == null) {
  31. break;
  32. }
  33. System.out.println(temp);
  34. temp = temp.next;
  35. }
  36. }
  37. }

进行测试:

  1. package LinkedList;
  2. public class SingleLinkedListDemo {
  3. public static void main(String[] args) {
  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. HeroNode node4 = new HeroNode(4, "顾天乐", "金刚小铁拳");
  10. //进行添加操作
  11. list.addNode(node3);
  12. list.addNode(node1);
  13. list.addNode(node2);
  14. //不能进行重复添加相同的节点,可能会发生死循环
  15. //list.addNode(node3);
  16. list.addNode(node4);
  17. //进行遍历链表
  18. list.showList();
  19. }
  20. }





运行结果:
QQ截图20200723110915.jpg

第二种添加方式:根据id属性排序添加

QQ截图20200724095127.jpg

说明:addNodeById()是根据id号链式添加的方法,根据id号从小到大进行添加。
首先需要一个辅助节点temp也就是进行插入节点位置的前一个节点。
每次插入相当于遍历链表,从中找出与id位置相匹配的位置插入,在进行遍历判断过程中,会有三种情况,
第一种没有找到插入中间位置,插入到尾部,使用temp.next==null判断。
第二种插入id与原有链表中的id相同,提示无法插入,使用temp.next.id==node.id。
第三种找到在链表中插入的位置,使用temp.next.id>node.id来判断

插入过程说明:
插入只有两种情况也就是插入到尾部,另一种是插入到链表中,temp代表的插入位置之前的一个节点,那么首先得先将temp的next值赋给node的next也就是node.next = temp.next,
(注意不能直接将node作为temp的next值,这样的话原先temp的next值就会被覆盖从而丢失)
接着将node值赋给temp的next位置,temp.next = node。

flag为true实际代表着链表无法插入id相同的情况,为false代表可以插入,如插入到尾部或者链表中间。

  1. //创建管理链表的类
  2. class SingleLinkedList{
  3. ...
  4. public void addNodeById(HeroNode node) {
  5. //temp就代表待插入节点的前一个节点
  6. HeroNode temp = head;
  7. //这个flag代表是否有相同id值,true表示不能插入,false表示能插入
  8. boolean flag = false;
  9. while(true) {
  10. //当temp节点的下一个节点为null,说明在尾部了,跳出循坏在尾部添加
  11. if(temp.next == null) {
  12. break;
  13. }
  14. //当temp节点下一个节点的id与待插入节点的id相同,flag设置为true,表明不能插入
  15. if(temp.next.id == node.id) {
  16. flag = true;
  17. break;
  18. }
  19. //当temp节点下一个节点的id大于待插入节点id,跳出循环,将node节点插入到temp之后
  20. if(temp.next.id > node.id) {
  21. break;
  22. }
  23. //方便进行遍历
  24. temp = temp.next;
  25. }
  26. if(flag) {
  27. System.out.println("插入的节点id已存在,无法插入");
  28. }else {
  29. //首先插入节点的next先获取到temp的next节点
  30. node.next = temp.next;
  31. //再将node节点插入到temp节点之前
  32. temp.next = node;
  33. }
  34. }

测试过程:

  1. package LinkedList;
  2. public class SingleLinkedListDemo {
  3. public static void main(String[] args) {
  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. HeroNode node4 = new HeroNode(4, "顾天乐", "金刚小铁拳");
  10. //进行添加操作
  11. list.addNodeById(node3);
  12. list.addNodeById(node2);
  13. list.addNodeById(node1);
  14. list.addNodeById(node4);
  15. list.addNodeById(node1);
  16. //进行遍历链表
  17. System.out.println("遍历结果:");
  18. list.showList();
  19. }
  20. }

运行结果:
QQ截图20200724100731.jpg

修改节点方法(根据id)

QQ截图20200726100827.jpg

说明:**依旧使用一个辅助节点temp,并且进行遍历修改
首先判断是否为空链表,是的话直接退出。
② **在遍历过程中,有两种情况,一种是没有遍历到结尾没有找到修改位置,第二种情况,找到对应id位置进行修改,这里使用flag(true表示能够修改,false表示不能修改)

  1. //创建管理链表的类
  2. class SingleLinkedList{
  3. ...
  4. // 修改节点,根据id编号进行修改
  5. public void updateNode(HeroNode newnode) {
  6. // 创建一个辅助节点,令temp为head的next
  7. HeroNode temp = head.next;
  8. // 判断是否为空链表情况
  9. if (temp == null) {
  10. System.out.println("链表为空,无法进行修改!");
  11. return;
  12. }
  13. // true表示能够修改, false表示不能修改
  14. boolean flag = false;
  15. while (true) {
  16. // 表示到达链表末尾,无法进行插入
  17. if (temp == null) {
  18. break;
  19. }
  20. // 找到对应id相同的英雄
  21. if (temp.id == newnode.id) {
  22. flag = true;// 表示能够进行修改
  23. break;
  24. }
  25. // 进行遍历使用
  26. temp = temp.next;
  27. }
  28. if (flag) {
  29. temp.name = newnode.name;
  30. temp.nickname = newnode.nickname;
  31. System.out.printf("修改id【%d】成功!\n",newnode.id);
  32. } else {
  33. System.out.println("链表中无法找到对应id英雄,修改失败!");
  34. }
  35. }
  36. }


测试过程:**

  1. package LinkedList;
  2. public class SingleLinkedListDemo {
  3. public static void main(String[] args) {
  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. HeroNode node4 = new HeroNode(4, "顾天乐", "金刚小铁拳");
  10. //进行添加操作
  11. list.addNodeById(node3);
  12. list.addNodeById(node2);
  13. list.addNodeById(node1);
  14. list.addNodeById(node4);
  15. //进行遍历链表
  16. System.out.println("修改前遍历结果:");
  17. list.showList();
  18. //进行修改操作
  19. list.updateNode(new HeroNode(1, "小丁", "青龙偃月刀"));
  20. //list.updateNode(new HeroNode(6, "小丁", "青龙偃月刀"));
  21. System.out.printf("\n修改后遍历结果:\n");
  22. list.showList();
  23. }
  24. }

运行结果:
QQ截图20200724103007.jpgQQ截图20200724103044.jpg

删除节点方法

QQ截图20200726100849.jpg
说明:想要删除节点,需要找到删除节点的前一个节点,再进行删除即可。

  1. class SingleLinkedList{
  2. ...
  3. //根据id来删除节点
  4. public void delNode(int id) {
  5. if(head.next == null) {
  6. System.out.println("链表为空,无法删除!");
  7. return;
  8. }
  9. HeroNode temp = head;
  10. //true用来表示找到删除的节点,false表示未找到删除的节点
  11. boolean flag = false;
  12. while(true) {
  13. //到链表末尾没有找到对应节点
  14. if(temp.next == null) {
  15. break;
  16. }
  17. //找到temp的下一节点为对应删除的节点
  18. if(temp.next.id == id) {
  19. flag = true;
  20. break;
  21. }
  22. //进行遍历
  23. temp = temp.next;
  24. }
  25. if(flag) {
  26. temp.next = temp.next.next;
  27. System.out.printf("成功删除id【%d】",id);
  28. System.out.println();
  29. }else {
  30. System.out.printf("链表中未找到对应id【%d】节点,删除失败!",id);
  31. System.out.println();
  32. }
  33. }
  34. }

测试运行:

  1. package LinkedList;
  2. public class SingleLinkedListDemo {
  3. public static void main(String[] args) {
  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. HeroNode node4 = new HeroNode(4, "顾天乐", "金刚小铁拳");
  10. //进行添加操作
  11. list.addNodeById(node3);
  12. list.addNodeById(node2);
  13. list.addNodeById(node1);
  14. list.addNodeById(node4);
  15. //进行遍历链表
  16. System.out.println("修改前遍历结果:");
  17. list.showList();
  18. //进行删除操作
  19. list.delNode(5);//删除链表中不存在的情况
  20. list.delNode(1);//删除链表中的节点情况
  21. list.delNode(2);
  22. list.delNode(3);
  23. list.delNode(4);
  24. list.delNode(2);//空链表时删除情况
  25. System.out.printf("\n修改后遍历结果:\n");
  26. list.showList();
  27. }
  28. }

运行结果:
QQ截图20200726101146.jpg


三、单链表的常见面试题

① 求单链表中有效节点的个数

源代码SingleLinkedListDemo.java

QQ截图20200727100656.jpg

  1. public class SingleLinkedListDemo {
  2. //获取链表中的个数
  3. public static int getLinedListSize(HeroNode head) {
  4. //判断链表为空的情况
  5. if(head.next == null) {
  6. return 0;
  7. }
  8. //使用辅助节点
  9. int length = 0;
  10. HeroNode temp = head.next;
  11. while(temp != null) {
  12. length++;
  13. temp = temp.next;
  14. }
  15. return length;
  16. }
  17. }

测试代码:
说明:配合之前单链表的应用来进行测试

  1. public class SingleLinkedListDemo {
  2. public static void main(String[] args) {
  3. SingleLinkedList list = new SingleLinkedList();
  4. //创建四个节点
  5. HeroNode node1 = new HeroNode(1, "丁明", "青龙偃妈刀");
  6. HeroNode node2 = new HeroNode(2, "小殷", "优雅男神");
  7. HeroNode node3 = new HeroNode(3, "单泽鹏", "马尾小萝莉");
  8. HeroNode node4 = new HeroNode(4, "顾天乐", "金刚小铁拳");
  9. //进行添加操作
  10. list.addNodeById(node3);
  11. list.addNodeById(node2);
  12. list.addNodeById(node1);
  13. list.addNodeById(node4);
  14. //获取链表中的个数
  15. int size = SingleLinkedListDemo.getLinedListSize(list.head);
  16. System.out.printf("链表中的节点有%d个。\n",size);
  17. }
  18. }

运行结果:
QQ截图20200727100913.jpg

② 查找单链表中的倒数第 k 个结点 【新浪面试题】

源代码:SingleLinkedListDemo.java

QQ截图20200727102513.jpg

  1. public class SingleLinkedListDemo {
  2. //获取链表的倒数第k个节点
  3. public static HeroNode getLastIndexNode(HeroNode head,int k) {
  4. //判断链表为空的情况
  5. if(head.next == null) {
  6. return null;
  7. }
  8. //获取链表中的个数
  9. int size = SingleLinkedListDemo.getLinedListSize(head);
  10. //对于k的取值进行判断,若位置不合理返回null
  11. if(k<=0 || k>size) {
  12. return null;
  13. }
  14. //合理情况下求链表中的第size-k个节点
  15. //使用一个辅助节点
  16. HeroNode temp = head.next;
  17. for(int i=1;i<=size-k;i++) {
  18. temp = temp.next;
  19. }
  20. return temp;
  21. }
  22. }

测试代码:

  1. package LinkedList;
  2. public class SingleLinkedListDemo {
  3. public static void main(String[] args) {
  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. HeroNode node4 = new HeroNode(4, "顾天乐", "金刚小铁拳");
  10. //进行添加操作 这里是按照id进行添加的
  11. list.addNodeById(node3);
  12. list.addNodeById(node2);
  13. list.addNodeById(node1);
  14. list.addNodeById(node4);
  15. //获取链表中的个数
  16. int size = SingleLinkedListDemo.getLinedListSize(list.head);
  17. System.out.printf("链表中的节点有%d个。\n",size);
  18. //进行遍历链表
  19. System.out.println("进行遍历链表:");
  20. list.showList();
  21. System.out.println();
  22. //获取链表的倒数第4个节点
  23. HeroNode node = SingleLinkedListDemo.getLastIndexNode(list.head, 4);
  24. System.out.println("倒数第一个节点为:"+node);
  25. }
  26. }

运行结果:
QQ截图20200727101634.jpg

③ 单链表的反转【腾讯面试题,有点难度】

源代码:SingleLinkedListDemo.java

QQ截图20200727125100.jpg

思路说明:
QQ截图20200727125636.jpg

  1. public class SingleLinkedListDemo {
  2. //反转链表
  3. public static void reverseList(HeroNode head) {
  4. //当链表为空或1个节点时不进行反转
  5. if(head.next == null || head.next.next == null) {
  6. System.out.println("当前链表为空或有一个元素,无法反转!");
  7. return;
  8. }
  9. //设置其为当前指向节点
  10. HeroNode curNode = head.next;
  11. //设置其为当前指向节点的后一节点
  12. HeroNode nextNode = null;
  13. //设置一个反转的链表,用于存放之后遍历的链表
  14. HeroNode reversehead = new HeroNode(0, "", "");
  15. while(curNode!=null) {
  16. nextNode = curNode.next;//用于获取到当前节点cur后一个节点
  17. curNode.next = reversehead.next;//这段代码相当于将每次获取的节点从后往前存放
  18. reversehead.next = curNode;//用于保存存入到reverse后的节点
  19. curNode = nextNode;//让curNode获取到nextNode的值,就像向后遍历一样
  20. }
  21. head.next = reversehead.next;//将反转过后的链表接连到初始链表head上
  22. System.out.println("链表反转成功");
  23. System.out.println();
  24. }
  25. }

测试代码:

  1. package LinkedList;
  2. public class SingleLinkedListDemo {
  3. public static void main(String[] args) {
  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. HeroNode node4 = new HeroNode(4, "顾天乐", "金刚小铁拳");
  10. //进行添加操作 这里是按照id进行添加的
  11. list.addNodeById(node3);
  12. list.addNodeById(node2);
  13. list.addNodeById(node1);
  14. list.addNodeById(node4);
  15. //进行遍历链表
  16. System.out.println("进行遍历链表:");
  17. list.showList();
  18. System.out.println();
  19. //链表进行反转操作
  20. reverseList(list.head);
  21. System.out.println("链表反转之后:");
  22. list.showList();
  23. }
  24. }

运行结果:
QQ截图20200727125826.jpg

④ 从尾到头打印单链表 【百度,要求方式 1:反向遍历 。 方式 2:Stack 栈】

源代码:SingleLinkedListDemo.java

思路分析:
QQ截图20200728102411.jpg

这里演示方式2:使用栈进行存储再打印

  1. package LinkedList;
  2. import java.util.Stack;
  3. public class SingleLinkedListDemo {
  4. //将链表从后往前打印
  5. //使用到栈
  6. public static void reversedPrint(HeroNode head) {
  7. //判断链表是否为空
  8. if(head.next == null) {
  9. System.out.println("链表为空,无法打印");
  10. return;
  11. }
  12. //设置一个辅助节点
  13. HeroNode cur = head.next;
  14. //使用栈的api
  15. Stack<HeroNode> stack = new Stack<HeroNode>();
  16. while(cur != null) {
  17. stack.add(cur);
  18. //链表不断向后遍历,并压入栈
  19. cur = cur.next;
  20. }
  21. //进行遍历栈
  22. while(stack.size()>0) {
  23. System.out.println(stack.pop());
  24. }
  25. }
  26. }

测试过程:

说明:在遍历链表过程中不断存入栈中,最后再遍历栈中的数据,这样能够使链表的结构不变化**

  1. package LinkedList;
  2. import java.util.Stack;
  3. public class SingleLinkedListDemo {
  4. public static void main(String[] args) {
  5. SingleLinkedList list = new SingleLinkedList();
  6. //创建四个节点
  7. HeroNode node1 = new HeroNode(1, "丁明", "青龙偃妈刀");
  8. HeroNode node2 = new HeroNode(2, "小殷", "优雅男神");
  9. HeroNode node3 = new HeroNode(3, "单泽鹏", "马尾小萝莉");
  10. HeroNode node4 = new HeroNode(4, "顾天乐", "金刚小铁拳");
  11. //进行添加操作 这里是按照id进行添加的
  12. list.addNode(node3);
  13. list.addNode(node2);
  14. list.addNode(node1);
  15. list.addNode(node4);
  16. //进行遍历链表
  17. System.out.println("进行遍历链表:");
  18. list.showList();
  19. System.out.println();
  20. //将链表从后往前打印输出
  21. System.out.println("链表从后往前打印:");
  22. SingleLinkedListDemo.reversedPrint(list.head);
  23. }
  24. }

运行结果:
QQ截图20200728102353.jpg


四、双向链表的应用实例

源代码:DoubleLinkedListDemo.java
**

思路分析(双向链表与单链表的不同)

QQ截图20200728104339.jpg

代码实现

说明:此次代码实现了增加节点(顺序)、增加节点(按照id小到大)、删除节点、更改节点、遍历链表、获取头节点。

有无变化说明
对于遍历节点、修改节点对于单链表都没有变化。
有变化的说明:
增加节点(顺序):需要给新增节点的pre赋值
增加节点(按照id):由单链表给两个next赋值改变为两个pre以及两个next,并且对于temp.next.pre这个节点的pre在链表最后位置添加时要注意空指针的情况,所以要加上判断。
删除节点:单链表需要查找删除节点的前一个节点,而双链表可以直接查找到对应节点利用pre进行删除节点,对于获取到temp.next.pre也要进行null值判断进行赋值。

  1. package LinkedList;
  2. public class DoubleLinkedListDemo {
  3. public static void main(String[] args) {
  4. DoubleLinkedList list = new DoubleLinkedList();
  5. //创建四个节点
  6. HeroNode2 node1 = new HeroNode2(1, "丁明", "青龙偃妈刀");
  7. HeroNode2 node2 = new HeroNode2(2, "小殷", "优雅男神");
  8. HeroNode2 node3 = new HeroNode2(3, "单泽鹏", "马尾小萝莉");
  9. HeroNode2 node4 = new HeroNode2(4, "顾天乐", "金刚小铁拳");
  10. //进行添加操作 这里是按照id进行添加的
  11. list.addNode(node1);
  12. list.addNode(node2);
  13. list.addNode(node3);
  14. list.addNode(node4);
  15. System.out.println("进行修改操作:");
  16. HeroNode2 node5 = new HeroNode2(3, "长路", "无敌拳霸");
  17. list.updateNode(node5);
  18. //进行遍历操作
  19. list.showList();
  20. System.out.println();
  21. System.out.println("进行删除操作:");
  22. list.delNode(4);
  23. list.showList();
  24. System.out.println();
  25. System.out.println("进行插入(根据id)操作:");
  26. list.addNodeById(new HeroNode2(0, "林儿", "长路对象"));
  27. list.showList();
  28. }
  29. }
  30. //创建管理链表的类
  31. class DoubleLinkedList{
  32. //先初始化一个头节点, 头节点不要动, 不存放具体的数据
  33. public HeroNode2 head = new HeroNode2(0, "", "");
  34. //返回头节点
  35. public HeroNode2 getHeadNode() {
  36. return head;
  37. }
  38. //进行添加节点到末尾
  39. public void addNode(HeroNode2 node) {
  40. //首先使用一个临时变量代替head
  41. HeroNode2 temp = head;
  42. //寻找到链表的最底部
  43. while(true) {
  44. if(temp.next == null) {
  45. break;
  46. }
  47. temp = temp.next;
  48. }
  49. //插入node节点到最底部
  50. temp.next = node;
  51. //增加点:将最后添加的节点的pre得到temp节点
  52. node.pre = temp;
  53. }
  54. //插入节点按照id编号排序进行插入
  55. public void addNodeById(HeroNode2 node) {
  56. //temp就代表待插入节点的前一个节点
  57. HeroNode2 temp = head;
  58. //这个flag代表是否有相同id值,true表示不能插入,false表示能插入
  59. boolean flag = false;
  60. while(true) {
  61. //当temp节点的下一个节点为null,说明在尾部了,跳出循坏在尾部添加
  62. if(temp.next == null) {
  63. break;
  64. }
  65. //当temp节点下一个节点的id与待插入节点的id相同,flag设置为true,表明不能插入
  66. if(temp.next.id == node.id) {
  67. flag = true;
  68. break;
  69. }
  70. //当temp节点下一个节点的id大于待插入节点id,跳出循环,将node节点插入到temp之后
  71. if(temp.next.id > node.id) {
  72. break;
  73. }
  74. //方便进行遍历
  75. temp = temp.next;
  76. }
  77. if(flag) {
  78. System.out.printf("插入的节点id【%d】已存在,无法插入\n",node.id);
  79. }else {
  80. if(temp.next != null) {
  81. //若不是最后一个节点,那么就可以获取到temp.next并赋值
  82. temp.next.pre = node;
  83. }
  84. //首先插入节点的next先获取到temp的next节点
  85. node.next = temp.next;
  86. //插入的节点的前一个节点为temp
  87. node.pre = temp;
  88. //再将node节点插入到temp节点之前
  89. temp.next = node;
  90. }
  91. }
  92. //遍历链表
  93. public void showList() {
  94. //判断链表是否为空
  95. if(head.next == null) {
  96. System.out.println("链表为空,无法进行遍历数据!");
  97. return;
  98. }
  99. //使用一个临时变量获取到链表下一个节点
  100. HeroNode2 temp = head.next;
  101. while(true) {
  102. if(temp == null) {
  103. break;
  104. }
  105. System.out.println(temp);
  106. temp = temp.next;
  107. }
  108. }
  109. // 修改节点,根据id编号进行修改
  110. public void updateNode(HeroNode2 newnode) {
  111. // 创建一个辅助节点,令temp为head的next
  112. HeroNode2 temp = head.next;
  113. // 判断是否为空链表情况
  114. if (temp == null) {
  115. System.out.println("链表为空,无法进行修改!");
  116. return;
  117. }
  118. // true表示能够修改, false表示不能修改
  119. boolean flag = false;
  120. while (true) {
  121. // 表示到达链表末尾,无法进行插入
  122. if (temp == null) {
  123. break;
  124. }
  125. // 找到对应id相同的英雄
  126. if (temp.id == newnode.id) {
  127. flag = true;// 表示能够进行修改
  128. break;
  129. }
  130. // 进行遍历使用
  131. temp = temp.next;
  132. }
  133. if (flag) {
  134. temp.name = newnode.name;
  135. temp.nickname = newnode.nickname;
  136. System.out.printf("修改id【%d】成功!\n",newnode.id);
  137. } else {
  138. System.out.println("链表中无法找到对应id英雄,修改失败!");
  139. }
  140. }
  141. //根据id来删除节点 由于是双向链表就可以查找对应要删除的节点即可进行删除
  142. public void delNode(int id) {
  143. if(head.next == null) {
  144. System.out.println("链表为空,无法删除!");
  145. return;
  146. }
  147. //直接获取到头节点的下一个节点
  148. HeroNode2 temp = head.next;
  149. //true用来表示找到删除的节点,false表示未找到删除的节点
  150. boolean flag = false;
  151. while(true) {
  152. //到链表末尾没有找到对应节点
  153. if(temp == null) {
  154. break;
  155. }
  156. //找到temp的下一节点为对应删除的节点
  157. if(temp.id == id) {
  158. flag = true;
  159. break;
  160. }
  161. //进行遍历
  162. temp = temp.next;
  163. }
  164. if(flag) {
  165. //删除节点的前一个节点的next为下一个节点
  166. temp.pre.next = temp.next;
  167. //为删除节点的后一个节点的pre赋值
  168. //避免删除项是最后一个节点的情况
  169. if(temp.next != null) {
  170. temp.next.pre = temp.pre;
  171. }
  172. System.out.printf("成功删除id【%d】",id);
  173. System.out.println();
  174. }else {
  175. System.out.printf("链表中未找到对应id【%d】节点,删除失败!",id);
  176. System.out.println();
  177. }
  178. }
  179. }
  180. //创建节点
  181. class HeroNode2{
  182. public int id;
  183. public String name;
  184. public String nickname;
  185. public HeroNode2 next;//指向后面一个节点
  186. public HeroNode2 pre;//指向前面一个节点
  187. //创建一个构造器,存放id、name、nickname
  188. public HeroNode2(int id, String name, String nickname) {
  189. super();
  190. this.id = id;
  191. this.name = name;
  192. this.nickname = nickname;
  193. }
  194. @Override
  195. public String toString() {
  196. return "HeroNode2 [id=" + id + ", name=" + name + ", nickname=" + nickname + "]";
  197. }
  198. }

运行结果:
QQ截图20200805155009.jpg


五、单向环形链表

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

QQ截图20200805160804.jpg

2. 单向环形链表介绍

源代码:Josepfo.java

示意图:
QQ截图20200805160846.jpg

① Josephu 问题

问题描述:
QQ截图20200805160943.jpgQQ截图20200805160952.jpg

② 构建约瑟夫环以及遍历约瑟夫环(代码实现)

QQ截图20200805165622.jpg

创建约瑟夫环的节点以及约瑟夫环对象

  1. //表示约瑟夫环
  2. class CircleSingleLinkedList{
  3. //需要创建第一个节点
  4. private Boy first = null;
  5. }
  6. class Boy{
  7. private Integer id;
  8. private Boy next;
  9. public Boy(Integer id) {
  10. super();
  11. this.id = id;
  12. }
  13. public Integer getId() {
  14. return id;
  15. }
  16. public void setId(Integer id) {
  17. this.id = id;
  18. }
  19. public Boy getNext() {
  20. return next;
  21. }
  22. public void setNext(Boy next) {
  23. this.next = next;
  24. }
  25. }

创建约瑟夫环


说明:对于插入有两种情况,第一种是插入第一个节点时,第二种就是插入第二个节点之后所有节点。
需要的节点:**每次插入时创建的节点以及辅助节点(用于指向新创建的节点位置,不对first节点进行更改)。

  1. //表示约瑟夫环
  2. class CircleSingleLinkedList{
  3. //需要创建第一个节点
  4. private Boy first = null;
  5. public void createJosepho(int num) {
  6. //对创建个数进行规范
  7. if(num<1) {
  8. System.out.println("创建人数过少,请重新创建!");
  9. return;
  10. }
  11. //创建一个辅助节点
  12. Boy curBoy = null;
  13. //进行循环插入
  14. for(int i=1;i<=num;i++) {
  15. Boy boy = new Boy(i);
  16. //如果是第一个创建的
  17. if(i == 1) {
  18. first = boy;
  19. first.setNext(first);
  20. //辅助节点获取到新插入的节点
  21. curBoy = first;
  22. }else {
  23. //不是第一次插入时根据辅助节点插入
  24. curBoy.setNext(boy);
  25. boy.setNext(first);
  26. curBoy = boy;
  27. }
  28. }
  29. System.out.printf("%d人的约瑟夫环创建成功\n",num);
  30. }
  31. }

遍历约瑟夫环


说明:**需要判断链表是否为空以及使用辅助节点来进行遍历,遍历时的条件是当前节点的下一个节点为first第一个节点时遍历结束。

  1. //进行遍历约瑟夫环
  2. public void showJosepho() {
  3. //判断是否为空
  4. if(first == null) {
  5. System.out.println("约瑟夫环为空,无法遍历!");
  6. return;
  7. }
  8. //使用辅助节点来进行遍历
  9. Boy curBoy = first;
  10. while(true) {
  11. System.out.println("id:"+ curBoy.getId());
  12. if(curBoy.getNext() == first) {
  13. break;
  14. }
  15. curBoy = curBoy.getNext();
  16. }
  17. }

测试创建与遍历

  1. package LinkedList;
  2. public class Josepfo {
  3. public static void main(String[] args) {
  4. //创建数量为5的约瑟夫环
  5. CircleSingleLinkedList list = new CircleSingleLinkedList();
  6. list.createJosepho(5);
  7. list.showJosepho();
  8. }
  9. }

运行结果:
QQ截图20200805170723.jpg

③ 小孩出圈(传入初始位置、数数数量、总人数)

QQ截图20200806104607.jpg

说明:接连在上面方法的类中,由于是单向链表,所以我们在出圈过程中还应该知晓出圈节点的前一个节点
① **初步状态辅助节点在整个环形链表最后。
② 根据初始位置来调整helper、first节点位置,始终一前一后状态。
③ 来根据数数进行出圈报出id名。**

  1. public void countBoy(int startNo,int countNum,int nums) {
  2. //如果链表为空,初始位置小于1,初始位置大于总数
  3. if(startNo<1 || startNo>nums || first == null) {
  4. System.out.println("您输入的参数有误,请重新输入!");
  5. }
  6. //定义一个辅助节点
  7. Boy helper = first;
  8. //将辅助节点变为最后一个节点来紧跟first头节点
  9. while(true) {
  10. if(helper.getNext() == first) {
  11. break;
  12. }
  13. helper = helper.getNext();
  14. }
  15. //根据初始位置进行调位
  16. for(int i = 0;i<startNo-1;i++) {
  17. //两个节点一起移动
  18. first = first.getNext();
  19. helper = helper.getNext();
  20. }
  21. while(true) {
  22. //如果环中只有一个节点了,那么退出循环
  23. if(helper == first) {
  24. break;
  25. }
  26. //进行数数
  27. for(int i=0;i<countNum-1;i++) {
  28. first = first.getNext();
  29. helper = helper.getNext();
  30. }
  31. //将first指定的移除
  32. System.out.println("移除的id为:"+first.getId());
  33. first = first.getNext();//这里first移到移除人的后方
  34. helper.setNext(first);//辅助节点的next值指向新的first
  35. }
  36. System.out.println("最后一位id为:"+first.getId());
  37. }

测试代码:

  1. package LinkedList;
  2. public class Josepfo {
  3. public static void main(String[] args) {
  4. //创建数量为5的约瑟夫环
  5. CircleSingleLinkedList list = new CircleSingleLinkedList();
  6. list.createJosepho(5);
  7. list.showJosepho();
  8. //起始位置为1,报数为2,总人数为5
  9. System.out.printf("\n开始数数了:\n");
  10. list.countBoy(1, 2, 5);
  11. }
  12. }

运行结果:
QQ截图20200806105003.jpg


整理者:长路 时间:?-2020/8/6