单循环链表之约瑟夫环详解

约瑟夫环

  先来看一个有关约瑟夫环的小故事(没兴趣的直接跳过)

据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。(这里我们将所有人杀死)

画了一个约瑟夫环的示例图解:

    理解起来比较容易,但是代码部分有细节需要考虑(当first指向的结点出列后,具体见下面代码分析)

  首先2号出列

  

  接着第n+2个点到,也就是4号小孩出列

  

  以此类推,出列1号小孩

  

   最终得到出列小孩依次为 2、4、1、5、3

代码部分:

  首先构建约瑟夫环(即循环单链表)

 1 private Boy cur=null;//这个辅助引用,代替头结点来完成循环链表的构建
 2 private Boy first=null;//头结点
 3 public void addBoy(int nums) {//传入多少人数
 4         if (nums < 2) {
 5             System.out.println("人数不得小于2人");
 6             return;
 7         }
 8         for (int i = 1; i <= nums; i++) {//遍历创建小孩结点
 9             Boy boy = new Boy(i);
10             if (i == 1) {
11                 first = boy;//将第一个结点赋给first
12                 first.setNext(first);//形成一个环
13                 cur = boy;//代替头结点完成接下来的链表构建
14             }
15             cur.setNext(boy);//连接新结点
16             boy.setNext(first);//形成环
17             cur = cur.getNext();//准备连接下一个结点
18         }
19         cur = cur.getNext();//让cur回到头结点处,以便后序操作
20     }

  链表已经构建完成,接下来实现约瑟夫环(思路如下图):

   约瑟夫环核心代码:

 1     /*
 2      * start:从第几个小孩开始报数
 3      * m:报数从first为1开始累计m人,则第m人出队,然后继续循环
 4      * nums:一共nums个小孩
 5      * 
 6      * 比如有编号为1,2,3,4,5个小孩,start为1,m为2. 则出队顺序为:2,4,1,5,3
 7      */
 8     public void countBoy(int start, int m, int nums) {
 9         addBoy(nums);
10 
11         Boy helper = first;
12 
13         while (true) {
14             if (helper.getNext() == first)
15                 break;
16             helper = helper.getNext();
17         }
18         // 从start开始报数
19         for (int i = 1; i < start; i++) {//重置头结点至start处
20             first = first.getNext();
21             helper = helper.getNext();
22         }
23         while (true) {
24             if (helper == first) {
25                 break;
26             }
27             System.out.println("出队编号为" + first.getNo());
28             first = first.getNext();//重置头结点为出列结点的下一个结点
29             helper.setNext(first);//之前first的后一个结点helper不再指向出列的结点,指向头结点
30             for (int i = 1; i < m; i++) {//此处要注意上面已经喊过一次“到”,这里少一次即可
31                 helper = helper.getNext();
32                 first = first.getNext();
33             }
34         }
35         System.out.println("出队编号为" + first.getNo());
36 
37     }
原文地址:https://www.cnblogs.com/taichiman/p/12843744.html