Josephu 问题:
假设有 n 个小孩围坐在一起(编号为 1, 2, …, n)。约定从编号为 k (1<=k<=n)的小孩从1开始报数,报数为 m 的那个小孩出列。他的下一位又从1开始报数,报数为 m 的那个小孩出列。以此类推,直到所有的小孩都出列。这样就会产生一个出列编号。
这里,我们可以使用单向循环链表 来解决这个问题。将这个 n 个小孩构成一个有 n 个节点单向循环链表,然后由 k 号节点从 1 开始计数,数到第 m 个,将其从链表中删除。然后从删除的这个节点的下一个节点重新开始计数…直到最后一个节点也被删除。
在删除节点之后,删除节点的上一个节点的next会指向删除节点的下一个节点。所以,我们可以考虑定义一个辅助变量 latter 来记录“上一个节点”。
首先,定义节点类Node
class Node{
private int no;
private Node next;
public Node(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
然后定义循环单链表,并声明约瑟夫问题的解决方法
class CircleSingleLinkedList{
private Node first;
//添加节点
public void add(int nums){
if (nums<1){
System.out.println("number is not reasonable");
return;
}
Node curBoy = null;
for (int i = 1; i <= nums; i++) {
Node boy = new Node(i);
if (i==1){
first = boy;
first.setNext(first) ;
curBoy = first;
}else {
curBoy.setNext(boy) ;
boy.setNext(first);
curBoy = boy;
}
}
}
//显示节点
public void show(){
if (first == null){
System.out.println("no boy");
return;
}
Node temp = first;
while (true){
System.out.printf("num %d boy\n",temp.getNo());
if (temp.getNext()!=first){
temp = temp.getNext();
}else {
break;
}
}
}
/**
* 约瑟夫问题(丢手绢问题)
* @param m 数m个
* @param k 从第k个小孩开始数
* @param n 总共有n个小孩
*/
public void josephu(int k,int m,int n){
//判断给的k值是否合理
if (first == null || k>n || k<1){
System.out.println("wrong !!");
return;
}
//添加完节点之后,first指向的是第一个节点
Node latter = first;
//将latter指向first的上一个节点
while (true){
if (latter.getNext() == first){
break;
}
latter = latter.getNext();
}
//将first指向第k个节点,这是题目中要求的
for (int i = 0 ; i < k-1;i++){
first = first.getNext();
latter = latter.getNext();
}
while (true){
//链表中只剩下一个节点的时候,latter和first指向的是同一个节点
if (latter == first){
break;
}
//将first指向报数为m的节点
for (int j =0 ; j< m-1 ; j++){
first = first.getNext();
latter = latter.getNext();
}
//打印出第m个节点的编号
System.out.printf("NO.%d child out\n",first.getNo());
//删除第m个节点
first = first.getNext();
latter.setNext(first);
}
//跳出循环的时候,latter和first指向的是同一个节点也是最后一个节点,直接打印编号即可。
System.out.printf("NO.%d child out\n",first.getNo());
}
}
最后测试一下吧。总共n=5个小孩,从编号k=1的小孩开始数m=2个小孩。
public class Josephu {
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.add(5);
//circleSingleLinkedList.show();
circleSingleLinkedList.josephu(1,2,5);
}
}
测试结果
**
输出顺序为 2 4 1 5 3