什么是约瑟夫问题
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?
约瑟夫的故事比较残忍,我们把约瑟夫问题改为一个小游戏,游戏规则如下:
假设编号为1,2,3...n个人手拉手围坐在一圈,约定编号为k(1<= k <= n)的人开始报数,数到m的人出圈,他的下一位又从1开始报数,数到m的人又出圈,依次类推,直到剩余一个人即为胜出者,由此产生一个出圈编号的序列,
使用循环链表模拟n个人围坐一圈
首先我们定义一个循环链表,由此来模拟n个人围坐在一圈。下面先看下单链表的实现代码
1 /** 2 * 单向循环链表 3 */ 4 class CycleSingleLinkedList{ 5 PersonalNode first = null; 6 /** 7 * 向循环链表中添加num个节点 8 * @param num 9 */ 10 public void add(int num){ 11 if(num < 1){ 12 System.out.printf("参数不合法"); 13 return; 14 } 15 PersonalNode curr = null; 16 for (int i = 1; i <=num; i++) { 17 PersonalNode personalNode = new PersonalNode(i); 18 if(i == 1){ 19 //添加第一个节点 20 first = personalNode; 21 curr = first; 22 curr.setNext(first); 23 }else { 24 curr.setNext(personalNode); 25 personalNode.setNext(first); 26 curr = personalNode; 27 } 28 } 29 30 } 31 32 } 33 34 /** 35 * 人物节点 36 */ 37 class PersonalNode { 38 private int no; 39 private PersonalNode next; 40 41 public PersonalNode(int no){ 42 this.no = no; 43 } 44 45 public int getNo() { 46 return no; 47 } 48 49 public void setNo(int no) { 50 this.no = no; 51 } 52 53 public PersonalNode getNext() { 54 return next; 55 } 56 57 public void setNext(PersonalNode next) { 58 this.next = next; 59 } 60 }
下面我们用图例的方式讲解一下添加n个节点的循环链表的流程,当创建第一个节点时,我们让first和current指向第一个节点,并将current的next指向first节点,以此来形成一个循环。当添加第二个节点时,我们将current的next指针指向第二个节点,第二个节点的next指向first,然后将current后移,即current指向第二个节点,以此类推。
现在我们问题的第一步即n个人围坐在一起解决了,下面来实现第二步从第k个人开始数数。第二步比较简单,即让first指针后移k-1次,由此让first指针指向第一个数数的人。但由于first指向的节点需要出圈,这里我们再定义一个helper指针,来指向first指针的前一个节点,即让helper指针首先指向first,依此循环直到helper的next指针等于first(因为是循环链表)。并保持helper指针始终在first指针的前一个节点(只有一个节点时与first指针指向同一个节点)。假设k=2即从第二个人开始数,m=3即数到3的人出圈,如下图所示
找到node4的人出圈,这时让helper的next等于first的next,然后让first等于helper的next,如下图所示
继续从node1开始数,数到3,即node3出圈,此时指针指向情况如下图
node3出圈后,下一个要出圈的为node1,依此循环,直到helper与first指针指向相同节点,即node2为最后一个要出圈的人。代码如下
1 /** 2 * 约瑟夫问题 3 * @param n n个人围成圈 4 * @param k 第k个人开始报数 5 * @param m 数到m的人出圈 6 */ 7 public void joseph(int n,int k,int m){ 8 //添加n个人 9 add(n); 10 //helper指针指向first的前一个节点 11 PersonalNode helper = first; 12 while (helper.getNext() != first){ 13 helper = helper.getNext(); 14 } 15 //找到第k个人 16 for (int i = 0; i < (k-1); i++) { 17 first = first.getNext(); 18 helper = helper.getNext(); 19 } 20 //数到m的人出圈 21 while (true){ 22 if(helper == first){ 23 break; 24 } 25 for (int i = 0; i < (m-1); i++) { 26 first = first.getNext(); 27 helper = helper.getNext(); 28 } 29 System.out.printf("编号为%d的人出圈\n",first.getNo()); 30 first = first.getNext(); 31 helper.setNext(first); 32 } 33 System.out.println("最后出圈的人是:"+first.getNo()); 34 35 }
测试代码
1 public static void main(String []args){ 2 CycleSingleLinkedList linkedList = new CycleSingleLinkedList(); 3 linkedList.joseph(4,2,3); 4 }
测试结果:
总结
以上就是实现约瑟夫问题的循环链表解决思路,当然肯定还有很多别的解决思路,欢迎留言探讨,谢谢。
原文地址:https://www.cnblogs.com/menglong1108/p/11617542.html