(单向环形链表)约瑟夫环


应用实例

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

image-20220716213807128

  • 提示

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

    • 构建一个单向的环形链表思路

      1. 先创建第一个节点,让 first 指向该节点,并形成环形
      2. 后面当我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可
    • 遍历环形链表

      1. 先让一个辅助指针(变量)curBoy,指向first节点
      2. 然后通过一个while循环遍历该环形链表即可 curBoy.next == first 结束
  • Joseph问题–>小孩出圈思路

    • 根据用户的输入,生成一个小孩出圈的顺序:n = 5 ,即有5个人;k = 1,从第一个人开始报数;m = 2,数2下
      1. 需求创建一个辅助指针(变量) helper ,事先应该指向环形链表的最后这个节点.
        补充:小孩报数前,先让first和 helper移动k-1次
      2. 当小孩报数时,让first和helper指针同时的移动 m -1
      3. 这时就可以将first指向的小孩节点出圈
        first = first.next
        helper.next =first
        原来first指向的节点就没有任何引用,就会被回收
      4. 出圈的顺序
        2->4->1->5->3

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package com.ysy.linkedlist;

public class Joseph {
public static void main(String[] args) {
//测试构建环形链表,并遍历
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(5);
circleSingleLinkedList.showBoy();

//测试小孩出圈
circleSingleLinkedList.countBoy(1, 2, 5);
}
}

//创建一个环形的单向链表
class CircleSingleLinkedList{
//创建一个first节点,当前没有编号
private Boy first = new Boy(-1);
//添加小孩节点,构成一个环形的单向链表
public void addBoy(int nums){
//nums做一个校验
if(nums < 1){
System.out.println("nums值不正确");
return;
}
Boy curBoy = null;//辅助指针,帮助构建环形链表
//使用for循环创建环形链表
for(int i = 1; i <= nums; i++){
//根据编号创建小孩节点
Boy boy = new Boy(i);
//如果i=1
if(i == 1){
first = boy;
first.setNext(first);//构成环
curBoy = first;//让curBoy指向第一个节点
}else{
curBoy.setNext(boy);
boy.setNext(first);
curBoy = boy;
}
}
}

//遍历当前环形链表
public void showBoy(){
//判断链表是否为空
if(first == null){
System.out.println("没有任何小孩~");
return;
}
//first不能动,使用辅助指针
Boy curBoy = first;
while (true){
System.out.printf("小孩的编号%d \n", curBoy.getNumber());
if(curBoy.getNext() == first){
//遍历完毕
break;
}
curBoy = curBoy.getNext();
}
}

//根据用户的输入,计算出小孩的出圈顺序

/**
*
* @param startNumber 表示从第几个小孩开始数数
* @param countNumber 表示数几下
* @param nums 表示小孩个数
*/
public void countBoy(int startNumber, int countNumber, int nums){
//先对数据校验
if(first == null || startNumber < 1 || startNumber > nums){
System.out.println("参数输入有误,请重新输入~");
return;
}

//辅助指针first,帮助小孩出圈
Boy helper = first;
//创建辅助指针helper,事先指向环形链表的最后一个节点
while (true){
if(helper.getNext() == first){
break;
}
helper = helper.getNext();
}

//小孩报数前,先让first和 helper移动k-1次
for(int j =0; j < startNumber - 1; j++){
first = first.getNext();
helper = helper.getNext();
}

//当小孩报数时,让first和helper指针同时的移动 m-1 次,然后出圈
//循环操作,直到圈中只有一个小孩
while (true) {
if(helper == first){
//只有一个小孩
break;
}
//让first和helper指针同时的移动 countNumber-1 次
for(int j = 0; j < countNumber - 1; j++){
first = first.getNext();
helper = helper.getNext();
}
//这时first指向的节点就是要出圈的小孩
System.out.printf("小孩%d出圈 \n", first.getNumber());
//小孩出圈
first = first.getNext();
helper.setNext(first);
}
System.out.printf("最后留在圈中的小孩编号为%d \n", first.getNumber());
}
}

//创建一个Boy类,表示一个节点
class Boy{
private int number;//编号
private Boy next;//指向下一个节点

public Boy(int number) {
this.number = number;
}

public int getNumber() {
return number;
}

public void setNumber(int number) {
this.number = number;
}

public Boy getNext() {
return next;
}

public void setNext(Boy next) {
this.next = next;
}
}

文章作者: Yang Shiyu
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yang Shiyu !
  目录