一、单向环形链表介绍
二、单向环形链表应用场景
Josephu(约瑟夫、约瑟夫环) 问题
问题描述:设编号为 1,2,...n 的 n 个人围坐一圈,约定编号为 k (1<=k<=n)的人从1开始报数,数到 m 的那个人出列,它的下一位又从1开始报数,数到 m 的那个人又出列,一次类推,直到所有人出列为止,由此产生一个出队编号的序列。
提示:用一个不带头节点的循环链表来处理约瑟夫环问题:先构成一个有 n个节点的单循环链表,然后由 k 节点起从 1 开始计数,计数到 m 时,对应节点从链表中删除,然后再从被删除节点的下一个节点又从1开始计数,直到最后一个节点从链表中删除结束。
三、Josephu 问题
1、约瑟夫的示意图
2、Josephu 问题
问题概述:设编号为1,2,...n 的n个人围坐一圈,约点编号为 k(1<=k<=n)的人从1开始报数,数到 m 的人出列,它的下一位又从 1 开始报数,数到 m 的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列
3、提示
用一个不带头节点的循环链表来处理约瑟夫环问题:先构成一个有 n个节点的单循环链表,然后由 k 节点起从 1 开始计数,计数到 m 时,对应节点从链表中删除,然后再从被删除节点的下一个节点又从1开始计数,直到最后一个节点从链表中删除结束。
4、约瑟夫问题 — 创建环形链表的思路图解
5、约瑟夫问题 — 小孩出圈的思路分析图
6、代码实现
1 package com.java.linkedlist;
2
3 public class Josepfu {
4
5 public static void main(String[] args) {
6 // 测试
7 // 构建环形链表
8 CircleSingleLinkedList list = new CircleSingleLinkedList();
9 list.addBoy(5); // 加入5个小孩节点
10 list.showBoy();
11
12 // 测试出圈
13 list.countBoy(1, 2, 5);
14
15 }
16
17 }
18
19 // 创建环形的单向链表
20 class CircleSingleLinkedList {
21 // 创建一个 first 节点,当前没编号
22 private Boy first = null;
23
24 // 添加小孩节点,构成一个环形的链表
25 public void addBoy(int nums) {
26 // nums 数据校验
27 if (nums < 1) {
28 System.out.println("nums的值不正确");
29 return;
30 }
31
32 Boy curBoy = null; // 辅助指针,帮助构建环形链表
33 // 使用for来创建环形链表
34 for (int i = 1; i <= nums; i++) {
35 // 根据编号,创建小孩节点
36 Boy boy = new Boy(i);
37 // 如果是第一个小孩
38 if (i == 1) {
39 first = boy;
40 first.setNext(first); // 构成环
41 curBoy = first; // 让 curBoy指向第一个小孩
42 } else {
43 curBoy.setNext(boy);
44 boy.setNext(first);
45 curBoy = boy;
46 }
47 }
48
49 }
50
51 // 遍历当前的环形链表
52 public void showBoy() {
53 // 判断链表是否为空
54 if (first == null) {
55 System.out.println("没有任何小孩");
56 return;
57 }
58
59 // 因为 first 不能动,使用辅助指针完成遍历
60 Boy curBoy = first;
61 while (true) {
62 System.out.printf("小孩的编号%d\n", curBoy.getNo());
63 if (curBoy.getNext() == first) { // 说明已经遍历完毕
64 break;
65 }
66 curBoy = curBoy.getNext(); // 让 curBoy 后移
67 }
68 }
69
70 // 根据用户的输入,计算出出圈的一个顺序
71 /**
72 *
73 * @param startNo 表示从第几个小孩开始数数
74 * @param countNum 表示数几下
75 * @param nums 表示最初有多少小孩在圈中
76 */
77 public void countBoy(int startNo, int countNum, int nums) {
78 // 先对数据进行校验
79 if (first == null || startNo < 1 || startNo > nums) {
80 System.out.println("参数输入有误,请重新输入");
81 return;
82 }
83
84 // 创建一个辅助指针,帮助完成小孩出圈
85 Boy helper = first;
86 // 需要创建一个辅助指针 helper,事先应该指向环形链表的最后这个节点
87 while (true) {
88 if (helper.getNext() == first) {
89 break;
90 }
91 helper = helper.getNext();
92 }
93
94 // 小孩报数前,先让 first和helper 移动k-1次
95 for (int j = 0; j < startNo - 1; j++) {
96 first = first.getNext();
97 helper = helper.getNext();
98 }
99
100 // 当小孩报数时,让first 和 helper 指针同时移动 m-1 次,然后出圈
101 // 循环操作,直到圈中只有一个节点
102 while (true) {
103 if (helper == first) { // 说明圈中只有一个节点
104 break;
105 }
106 // 让 first 和helper 指针同时移动 countNum -1 次
107 for (int j = 0; j < countNum - 1; j++) {
108 first = first.getNext();
109 helper = helper.getNext();
110 }
111 // 这时first 指向的节点,就是要出圈的小孩节点
112 System.out.printf("小孩%d出圈\n", first.getNo());
113 // 这时将 first 指向 的小孩节点出圈
114 first = first.getNext();
115 helper.setNext(first);
116 }
117
118 System.out.printf("最后留在圈中的小孩编号%d\n", first.getNo());
119 }
120 }
121
122 // 创建一个Boy类,表示一个节点
123 class Boy {
124 private int no; // 编号
125 private Boy next; // 指向下一个节点,默认null
126
127 // 构造方法
128 public Boy(int no) {
129 this.no = no;
130 }
131
132 public int getNo() {
133 return no;
134 }
135
136 public void setNo(int no) {
137 this.no = no;
138 }
139
140 public Boy getNext() {
141 return next;
142 }
143
144 public void setNext(Boy next) {
145 this.next = next;
146 }
147
148 }