约瑟夫问题:
设编号为1,2,…n的人围成一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人再出列,以此类推,直到所有人都出列,由此产生出一个出队列编号的序列。
这里n个人围成一圈,依次出队,这跟我们学到到的循环链表很像,因此我们就通过循环链表来模拟出队情况。
首先,需要设计一个节点类型Person,包含num和next两个属性:
class Person{
int num;//编号
Person next;//指向下一个人
}
然后创建链表,设计链表类时,我们在链表类中设计一个head节点,用于表示头节点,然后链表类需要包含添加指定个数节点和出队两个方法。
实现代码如下:
public class JosephuDemo {
public static void main(String[] args) {
SingleLinkedList linkedList = new SingleLinkedList();
linkedList.addPerson(5);//5个人
linkedList.out(1,2);//从第一个人开始,每次数到2出队
}
}
class SingleLinkedList{
private Person head = null;
/**
* 环形链表中添加nums个节点
* @param nums
*/
public void addPerson(int nums){
if(nums < 1){
System.out.println("nums的值不正确");
return;
}
Person tmp = null;//用于标识第几个节点
for (int i = 1; i <= nums; i++) {
Person p = new Person(i);
if(i == 1){
head = p;
head.next = head;
tmp = head;
}else {
tmp.next = p;
p.next = head;
tmp = tmp.next;
}
}
}
/**
* 约瑟夫出队
* @param k
* @param m
*/
public void out(int k, int m){
//定义一个tmp,相当于一个游标,用来遍历链表中的节点。首先指向头节点
Person tmp = head;
//遍历链表节点,找到编号为k的节点的前一个节点(因为这里是单链表,所以只有指向编号为k的前一个节点,在数数之后,才好对要出队的节点进行删除操作)
while (tmp.next.num != k) {
tmp = tmp.next;
}//循环结束到达编号为k的前一个人
//开始数数
while (tmp.next != tmp) {//当tmp.next == tmp时,说明当前链表中只剩下一个头节点,也就是圈中只有一个人了
//这里注意,由于数m个后,tmp指向了要出队的人,不好进行删除动作,所以只数m-1次
for (int i = 0; i < m - 1; i++) {
tmp = tmp.next;
}
System.out.println(tmp.next.num);
//tmp的下一个人出列
tmp.next = tmp.next.next;
}
//删除最后一个人
System.out.printf("最后出圈的人是%d" ,tmp.num);
tmp = null;
}
}
class Person{
int num;//编号
Person next;//指向下一个人
public Person(int num) {
this.num = num;
}
@Override
public String toString() {
return "Person{" +
"num=" + num +
'}';
}
}
代码运行结果: