单向环形链表

单向环形链表

单向环形链表

Josephu(约瑟夫)问题

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

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

构建一个单向环形链表思路
  • 先构建第一个节点,让first指向该节点,并形成环形
  • 后面当我们每创建一个节点,就把该节点,加入到已有的环形链表中即可
遍历该环形链表
  • 先让一个辅助指针(变量)curBoy,指向first节点
  • 然后通过一个while循环遍历该环形链表即可,curBoy.next=first结束
出圈思路
  • 需要创建一个辅助指针(变量)helper,事先应该指向环形链表的最后这个节点
  • 补充:小孩报数前,先让first和helper移动k-1次
  • 当小孩报数时,让first和helper指针同时移动m-1次
  • 这时就可以将first指向的小孩节点出圈
  • first=first.next
  • helper.next=first
  • 原来first指向的节点就没有任何引用,就会被回收
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
public class Josephu {
public static void main(String[] args) {
// 测试构建和遍历
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
int nums = 25;
circleSingleLinkedList.addBoy(nums);
circleSingleLinkedList.showBoy();

// 测试出圈
circleSingleLinkedList.countBoy(8, 2, nums);
}
}

//创建一个环形单向链表
class CircleSingleLinkedList {
// 创建一个first节点,当前没有编号
private Boy first = null;

// 添加节点,构建成一个环形链表
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);
// 如果是第一个节点
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.getNo());
if (curBoy.getNext() == first) {// 遍历完毕
break;
}
curBoy = curBoy.getNext();// curBoy后移
}
}

// 根据用户的输入,计算出小孩出圈的顺序
/**
*
* @param startNo 表示从第几个小孩开始数
* @param countNum 表示数几下
* @param nums 表示最初有多少个小孩在圈
*/
public void countBoy(int startNo, int countNum, int nums) {
// 先对数据进行校验
if (first == null || startNo < 1 || startNo > nums) {
System.out.println("参数输入有误~~~");
return;
}
// 创建一个辅助指针,帮助小孩出圈
Boy helper = first;
while (true) {
if (helper.getNext() == first) {// 说明helper指向最后
break;
}
helper = helper.getNext();
}
// 小孩报数前,先让first和helper移动k-1次
for (int i = 0; i < startNo - 1; i++) {
first = first.getNext();
helper = helper.getNext();
}
// 当小孩报数时,让first和helper指针同时移动m-1次,然后出圈
// 循环,直到圈中只有一个节点
while (true) {
if (helper == first) {
break;
}
// 让first和helper指针同时移动countNum-1,然后出圈
for (int j = 0; j < countNum - 1; j++) {
first = first.getNext();
helper = helper.getNext();
}
// 这时first指向的节点,就是要出圈的小孩节点
System.out.printf("小孩%d出圈\n", first.getNo());
// 这时将first指向的小孩节点出圈
first = first.getNext();
helper.setNext(first);
}
System.out.printf("最后留在圈中的小孩编号%d \n", first.getNo());
}
}

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

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

public int getNo() {
return no;
}

public void setNo(int no) {
this.no = no;
}

public Boy getNext() {
return next;
}

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

}