0 概述
- 2024年的春晚都看了吧,其中刘谦的魔术表演《守岁共此时》有没有把你惊艳到?第一反应是:啊?他,会魔法吧?后续看了魔术揭秘,有大佬指出其本质是约瑟夫环的问题。那么我们就来看看这个约瑟夫环到底是个啥吧。
- 约瑟夫问题(Josephus Problem)是一个著名的理论问题,属于数学和计算机科学中的离散数学部分。它描述了一个围坐成一圈的人们被逐个淘汰的过程,每次淘汰是按照固定的规则进行的。这个问题可以用递归或迭代的方法来解决。
- 下面我们来看一道经典的约瑟夫算法笔试题
1 约瑟夫算法题
- 约瑟夫(Josephu) 问题为:设编号为 1,2,…n 的 n 个人围坐一圈,约定编号为 k(1<=k<=n) 的人从 1 开始报数,数到 m 的那个人出列,它的下一位又从 1 开始报数,数到 m 的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
- 可以使用数组或链表来模拟这个过程。由于人们是围坐一圈的,使用链表更直观一些
2 代码实现
-
以下使用 LinkedList 来实现上诉需求
-
创建一个 Person 类,表示一个节点
/** * 人员节点类 * @Author Jasper * @Time 2024/2/14 * @公众号:EzCoding */ class Person { /** 编号 */ private int no; /** 指向下一个节点,默认 null */ private Person next; public Person(int no) { this.no = no; } public int getNo() { return no; } public void setNo(int no) { this.no = no; } public Person getNext() { return next; } public void setNext(Person next) { this.next = next; } }
-
创建一个环形单向链表,写出链表相关方法
/** * 人员节点类 * @Author Jasper * @Time 2024/2/14 * @公众号:EzCoding */ class CircleSingleLinkedList { // 创建一个first节点,当前没有编号 private Person first = new Person(-1); // 添加人员节点,构建出一个环形的链表 public void add (int nums) { // 对nums做校验 if (nums < 1) { System.out.println("nums值不正确"); return; } // 辅助指针 Person curPerson = null; for (int i = 1; i <= nums; i++) { // 根据编号创建人 Person person = new Person(i); // 如果是第一个人 if (i == 1) { first = person; first.setNext(first); // 构成环 curPerson = first; // 让curPerson指向第1个人员 } else { curPerson.setNext(person); person.setNext(first); curPerson = person; } } } // 遍历当前环形链表 public void show() { // 判空 if (first == null) { System.out.println("链表为空"); return; } // 因为first不能动,因此仍然使用辅助指针 Person curPerson = first; while (true) { System.out.printf("人员的编号 %d\n", curPerson.getNo()); if (curPerson.getNext() == first) { // 说明已经遍历完毕,break break; } curPerson = curPerson.getNext(); } } /** * 根据用户的输入,计算出出圈的顺序 * @param startNo 从第几个人开始数 * @param count 数几下 * @param nums 最初共有多少个人在圈中 */ public void out(int startNo, int count, int nums) { // 输入校验 if (first == null || startNo < 1 || startNo > nums) { System.out.println("参数输入有误"); return; } // 辅助指针 Person helper = first; // 事先指向环形链表的最后这个节点 while (true) { if (helper.getNext() == first) { // 说明helper指向了最后的人员节点 break; } helper = helper.getNext(); } // 当人报数前,先让 first 和 helper 移动k - 1次 for (int i = 0; i < startNo - 1; i++) { first = first.getNext(); helper = helper.getNext(); } // 让 first 和 helper 同时移动 m - 1 次,然后出圈 // 循环操作,直到圈中只有1个节点 while (true) { if (helper == first) { // 说明圈中只有1个节点 break; } // 让 first 和 helper 同时移动 m - 1 次 for (int i = 0; i < count - 1; i++) { first = first.getNext(); helper = helper.getNext(); } // 这时 first 指向的节点就是要出圈的人 System.out.printf("人员 %d 出圈\n", first.getNo()); first = first.getNext(); // first指向first的下一个节点 helper.setNext(first); } System.out.printf("最后留在圈中的人员编号:%d\n", first.getNo()); } }
-
测试类,假设一共 6 人,从编号 1 开始报数,每 3 次就出圈一个人
/** * 测试类 * @Author Jasper * @Time 2024/2/14 * @公众号:EzCoding */ public class JosePhu { public static void main(String[] args) { // 测试构建环形链表和遍历 CircleSingleLinkedList list = new CircleSingleLinkedList(); list.add(6); // 加入6个人员节点 list.show(); // 测试出圈 list.out(1, 3, 6); } }
-
输出结果:
人员的编号 1 人员的编号 2 人员的编号 3 人员的编号 4 人员的编号 5 人员的编号 6 人员 3 出圈 人员 6 出圈 人员 4 出圈 人员 2 出圈 人员 5 出圈 最后留在圈中的人员编号:1
-
可以看到出圈顺序为:3 --> 6 --> 4 --> 2 --> 5,最后编号为 1 的人留在圈中
- 以上就是约瑟夫问题的全部内容了,是不是感觉很神奇,赶快收藏起来学习。
- 创作不易,感谢阅读,若您喜欢这篇文章,不妨传承这份知识的力量,点个赞或关注我吧~
- 微信gzh:EzCoding