约瑟夫 问题描述:设编号为1、2、3... ...n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人先出列,他的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人都出列为止,由此产生一个出队编号的序列,求此序列。
算法思路:
先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,保存对应节点的编号,并将节点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表删除。
代码分析:
1.构建环形链表:
- 创建第一个结点,并让first指向该结点,并将该节点的next域指向自己形成环形。
- 每创建一个新节点,就把该节点加入到已有的环形链表中
2.遍历环形链表
- 定义一个辅助变量curBoy ,指向first节点
- 通过while循环找到最后一个加入环形链表的结点,并将curBoy指向这个节点,即:curBoy.next==first
- (添加这个辅助变量的目的是保存first的前趋,便于删除报数完毕后first指向的结点)
3.报数
- 每一轮报数时,使用for循环m次,每循环一次,first和curBoy同时往下一位报数的人的方向移动一次。
- 当循环完毕m次时,保存first的编号,然后删除first结点:
- int n=first.no
- first=first.next;
- curBoy.next=first
- 每一轮报数结束,使用数组存储出列的编号,当圈中只剩下一个人时,直接将其编号添加至数组,最后依次打印数组的值即为出队编号的序列。
代码实现
结点:
//创建一个Boy类,表示一个节点
class Boy{
private int no; //编号
private Boy next; //指向下一个结点,默认null
public Boy(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public Boy getNext() {
return next;
}
public void setNext(Boy next) {
this.next = next;
}
}
链表:
//创建一个环形的单向链表
class CircleLinkedList{
//创建一个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;
}else {
curBoy.setNext(boy);
curBoy=boy;
boy.setNext(first);
}
}
}
//遍历当前的环形链表
public void showBoy(){
//判断链表是否为空
if(first==null) {
System.out.println("链表为空,没有任何小孩!");
return;
}
Boy curBoy = first;
while (true){
System.out.printf("小孩的编号:%d\n",curBoy.getNo());
if(curBoy.getNext()==first){
//说明已经遍历完毕
break;
}
curBoy=curBoy.getNext(); //curBoy后移直到其next指向first
}
}
//根据用户的输入,计算出小孩的出圈顺序
/*
startNo 开始的小孩的编号
countNo 表示数几下
nums 表示最初的小孩的个数
*/
public int[] countBoy(int startNo,int countNo,int nums){
Boy boyFirst = first;
for (int i=1;i<startNo;i++)
boyFirst=boyFirst.getNext(); //遍历到开始报数的小孩
Boy pre=boyFirst; //记录每一轮开始报数的前一个小孩,也就是每一圈的最后一个小孩
for (int i=1;i<nums;i++)
pre = pre.getNext();
int[] boys = new int[nums]; //记录出圈的编号序列
for (int j=0;j<nums;j++) {
for (int i = 0; i < countNo - 1; i++) { //下一圈的开始和最后一起后移,一直移到出圈的小孩
pre = pre.getNext();
boyFirst = boyFirst.getNext();
}
boys[j] = boyFirst.getNo();//记录出圈的小孩编号
boyFirst=boyFirst.getNext();//删除出圈小孩的结点
pre.setNext(boyFirst);
}
return boys;
}
}