Josephu问题
Josephu问题为:
设编号为1, 2, …n的n个人围坐一圈,约定编号为k (1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
思路:
- 我们不妨创建一个单向环形链表来模拟这种情况,根据小孩的出列断开不同的节点,连接新的节点进行模拟。
代码:
package 链表;
public class JosePhu问题 {
public static void main(String[] args) {
CircleLinkedList circleLinkedList = new CircleLinkedList();
circleLinkedList.addBoy(5); //此时环形链表有五个节点
circleLinkedList.list();
circleLinkedList.countBoy(1,2,5);
}
}
class CircleLinkedList{
private Boy first = null; //初始头节点
//构建指定长度的环形链表
public void addBoy(int num){
Boy cur = null; //用来遍历的辅助指针
for(int i=1;i<=num;i++){
Boy boy = new Boy(i);
if(i==1){
//第一次构建需要自身指向自身
first = boy;
first.next = first;
cur = first;
}
else{
cur.next = boy; //将新建立的节点加入环形链表中,此时cur的位置就是原先创建的环形链表的尾端
boy.next = first; //将添加进来的新节点的尾部指向头节点,形成环状
cur = cur.next; //cur每次要移动到尾部
}
}
}
//按照添加顺序显示环形链表
public void list(){
if(first==null){
System.out.println("环形链表为空");
return;
}
Boy cur = first; //用来遍历的临时指针
while(true){
System.out.println(cur.num); //先打印
if(cur.next==first){ //如果其next指向头指针,则代表遍历完全
return;
}
cur = cur.next;
}
}
/*
* startNo 表示从第几个小孩开始数数
* countNum 表示数几下
* nums 表示最初有多少小孩在里面
* 这个是用来解决约瑟夫问题的方法,即指定小孩人数,第一次报数从哪个小孩开始,每数到几让谁出来 后,我们使用环形链表来模拟出队顺序
* */
public void countBoy(int startNo,int countNum,int nums){
/*
* 其实可以不用两次循环,使用一次循环来解决,即first向前移动时,help跟着移动,只不过移动的要比first少1即可;
* 但是这样的话对于countNum为0时则不行
* */
Boy help = first; //辅助指针,指向头指针的前一个位置
/*因为我们下面是设置头指针直接移动到我们要出队的节点,而又由于我们是一个单向环形队列,所以头指针位置要出队,那么我们
为了保存头指针位置,只能从头指针的前一个位置得到,所以我们设置辅助指针的目的就是为了通过其的next指针来得到删去的
头指针位置!!!! 所以其实这个也可以不设置,不过设置了会更加清晰*/
//先将辅助指针移动到尾部,因此此时头指针在头部,所以刚好形成前后关系
while(true){
if(help.next==first){
break;
}
help = help.next;
}
//因此从第startNo个小孩开始报数,所以头指针的实际位置需要改变
for (int i=0;i<startNo-1;i++){
first = first.next;
help = help.next; //help是头指针的前一个,所以也跟着动
}
//模拟报数出队,直到全部出完
while(true){
for (int i=0;i<countNum-1;i++){ //先报数,即头指针先移动到指定要出队的节点的位置
first = first.next;
help = help.next; //辅助指针跟着移动
}
System.out.println(first.num); //找到要出队的了
if(help==first){ //如果两个指针重合,则一定代表就剩下一个了,因为一个指针的下一个指针指向自己了!
break;
}
first = first.next; //将头指针此时指向的位置删除,并指向自己原先位置指向的的下一个位置
help.next = first; //将被删除的节点的上一个节点的next指向设置为删除位置的下一个位置 , 这样才起到删除作用
}
}
}
class Boy{
int num;
Boy next;
public Boy(int num) {
this.num = num;
}
public Boy() {
}
}