约瑟夫 问题描述:设编号为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; } }