约瑟夫问题
这是17世纪的法国数学家加斯帕在《数目的游戏问题》中讲的一个故事:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈,从第一个人开始依次报数,数到第九个人就将他扔入大海。该人后面的人从1开始重新报数,如此循环进行直到仅余15个人为止。问怎样排法,才能使每次投入大海的都是非教徒。
思路
1. 先建立一个类,有 id 和 isRemove
class Person { int id; boolean isRemove; public Person(int id, boolean isRemove) { this.id = id; this.isRemove = isRemove; } public boolean isRemove() { return isRemove; } public void setRemove(boolean isRemove) { this.isRemove = isRemove; } }
-
2.
class Circle { private ArrayList<Person> circle = new ArrayList<Person>(); private int amount; // 一共多少人 Circle(int amount) { this.amount = amount; for (int i = 0; i < amount; i++) { Person p = new Person(i + 1, false); circle.add(p); } } /** * * @param index * 最开始扔人的位置,比如第9人 * @param total * 共需要扔几次 */ public void getIndex(int index, int total) { // 起始位置 int currentIndex = -1; for (int t = 0; t < total; t++) { int notRemove = 0; //本来是notRemove != 9,写死不好 while (notRemove != index) { currentIndex++; // 或者用 currentIndex % amount 解决 if (currentIndex == amount) { currentIndex = 0; } if (!circle.get(currentIndex).isRemove) { notRemove++; } } // 将扔的人设为 true Person p = circle.get(currentIndex); p.setRemove(true); circle.set(currentIndex, p); System.out.printf("第 %-2d 次仍的人id是%4d/n", t + 1, p.id); } } }
代码建立一个容器来保存各个人,主要就是不移除这个人,而是把他状态设为setRemove(true),好处这样容器的大小保持不变。如果删掉这样人,容器大小每次减1,算起来麻烦
if (currentIndex == amount) { currentIndex = 0; }
本来是用 currentIndex % amount 实现的,不过本代码都是+1,肯定会有 == amount的情况,用置为0更好
结果
第 1 次仍的人id是 9 第 2 次仍的人id是 18 第 3 次仍的人id是 27 第 4 次仍的人id是 6 第 5 次仍的人id是 16 第 6 次仍的人id是 26 第 7 次仍的人id是 7 第 8 次仍的人id是 19 第 9 次仍的人id是 30 第 10 次仍的人id是 12 第 11 次仍的人id是 24 第 12 次仍的人id是 8 第 13 次仍的人id是 22 第 14 次仍的人id是 5 第 15 次仍的人id是 23
优化
也可以用数组实现,下标和 boolean 正好模拟上面的 Person 类
static void other() { boolean[] usaJapa = new boolean[30]; // 用类库初始化 Arrays.fill(usaJapa, true); int leftCount = usaJapa.length; int countNum = 0; int index = 0; int i = 0; while (leftCount > 15) { if (usaJapa[index]) { countNum++; } if (countNum == 9) { countNum = 0; usaJapa[index] = false; leftCount--; System.out.printf("第 %-2d 次仍的人id是%4d/n", ++i, index + 1); } index++; if (index == usaJapa.length) { index = 0; } } }
用链表实现,remove 其中的数据
class Circle { private LinkedList<Person> circle = new LinkedList<Person>(); private int amount; // 一共多少人 Circle(int amount) { this.amount = amount; for (int i = 0; i < amount; i++) { Person p = new Person(i + 1, false); circle.add(p); } } public void othergetIndex(int mark, int total) { // remain 是剩下的人数,total 是需要删除的人数 int remain = amount; int count = 0; int current = 0; int i = 0; while (remain > amount - total) { count++; // 注意这人如果达到 mark,比如第9个人 // 删掉第9个人后,再删第9个人,就是删原来的第10个人 if (count == mark) { // remove 返回的是删除的 Person Person p = circle.remove(current); System.out.printf("第 %-2d 次仍的人id是%4d/n", ++i, p.id); count = 0; remain--; } else { current++; } if (current == amount - i) { current = 0; } } } }
用 LinkedList 删除操作更快,
Person p = circle.remove(current);
remove 返回删除的元素
LinkedList<Integer> testList = new LinkedList<Integer>(); for (int i = 0; i < 10; i++) { testList.add(i); } for (int i = 0; i < 5; i++) { System.out.println(testList.remove(3)); } /** * 3 * 4 * 5 * 6 * 7 */
这段代码的输出可以看出,删除了第3个元素,后面元素往前移动
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/10393.html