约瑟夫环问题 拼搏现实的明天。 2022-03-01 04:12 217阅读 0赞 转载自[https://my.oschina.net/jack90john/blog/1791110?p=1][https_my.oschina.net_jack90john_blog_1791110_p_1] -------------------- # 一、概念 # 在开始正题之前,还是解释一下约瑟夫环是什么。约瑟夫环是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。通常解决这类问题时我们把编号从0~n-1,最后 \[1\] 结果+1即为原问题的解。 # 二、通过数组循环实现 # 最常规的思路就是通过一个数组,数组中每个元素都是一个人,然后对数组进行循环处理,每当数组中的人数到m时,将其标记为淘汰。直到最后数组中只有一个人未被淘汰。 所以,首先,我们需要一个计算方法,参数中有总数和淘汰数两个参数。 private static Integer compute(Integer total, Integer keyNumber) { } 第二步,我们需要一个长度为total的布尔值数组,数组的index就表示了第几个人,元素的true和false表示了这个人是否被淘汰。一开始我们需要将所有人都设置为未被淘汰。 /*开始时设置一个长度为总人数的数组,并将元素都设为true start*/ Boolean[] peopleFlags = new Boolean[total]; for (int i = 0; i < total; i++) { peopleFlags[i] = true; } /*开始时设置一个长度为总人数的数组,并将元素都设为true end*/ 接下来第三步,我们需要三个变量: 第一个变量记录还剩多少人为被淘汰,这个变量的初始值为总人数; 第二个变量记录数到了多少,当这个参数等于淘汰数时归零; 第三个参数记录当时数到了第几个人,当这个参数等于总人数时归零(因为是一个圈,所以最后一个人数完后又轮到第一个人数数) int peopleLeft = total; //剩下的人数 int count = 0; //计数器,每过一个人加一,加到keyNumber时归零 int index = 0; //标记从哪里开始 第四步就开始循环计算了,首先判断剩余的人数是否大于一,如果大于一进入循环,取index,如果这个人未被淘汰,则计数器加一,如果等于keyNumber则淘汰这个人,否则跳过计数继续,当index等于总人数时 while (peopleLeft > 1) { if (peopleFlags[index]) { //说明还没有被淘汰 计数器加1 count++; if (count == keyNumber) { count = 0; //计数器归0 peopleFlags[index] = false; //此人被淘汰 peopleLeft--;//未被淘汰的人数-1 } } index++; //当当前人等于总人数时,则又从第一人开始计数 if (index == total) { index = 0; } } 最后,计算结束后,数组中只有一个元素为true,而这个就是最后没被淘汰的那个人,现在我们就开开始找到是谁赢得了这次比赛。 //经过上面的循环,现在数组中被淘汰的人都标记为false,最后没被淘汰都人标记为true for (int j = 0; j < total; j++) { if (peopleFlags[j]) { return j + 1; } } return null; 我们验证一下结果: public static void main(String[] args) { int total = 10; int keyNumber = 3; Integer winner = compute(total, keyNumber); System.out.println(total + "个人围成一圈数数,数到" + keyNumber + "的被淘汰,最后剩下的是第" + winner + "个人。"); } ![123207_rEPC_2826058.png][] OK,第一种方法成功。 代码汇总如下: package com.csu.test; public class Test { public static void main(String[] args) { int total = 10; int keyNumber = 3; Integer winner = compute(total, keyNumber); System.out.println(total + "个人围成一圈数数,数到" + keyNumber + "的被淘汰,最后剩下的是第" + winner + "个人。"); } private static Integer compute(int total, int keyNumber) { /* 开始时设置一个长度为总人数的数组,并将元素都设为true start */ Boolean[] peopleFlags = new Boolean[total]; for (int i = 0; i < total; i++) { peopleFlags[i] = true; } // 第一个变量记录还剩多少人未被淘汰,这个变量的初始值为总人数; // 第二个变量记录数到了多少,当这个参数等于淘汰数时归零; // 第三个参数记录当时数到了第几个人,当这个参数等于总人数时归零(因为是一个圈,所以最后一个人数完后又轮到第一个人数数) int peopleLeft = total; // 剩下的人数 int count = 0; // 计数器,每过一个人加一,加到keyNumber时归零 int index = 0; // 标记从哪里开始 while (peopleLeft > 1) { if (peopleFlags[index]) { // 说明还没有被淘汰 计数器加1 count++; if (count == keyNumber) { count = 0; // 计数器归0 peopleFlags[index] = false; // 此人被淘汰 peopleLeft--;// 未被淘汰的人数-1 } } index++; // 当当前人等于总人数时,则又从第一人开始计数 if (index == total) { index = 0; } } // 经过上面的循环,现在数组中被淘汰的人都标记为false,最后没被淘汰都人标记为true for (int j = 0; j < total; j++) { if (peopleFlags[j]) { return j + 1; } } return null; } } -------------------- # 二、通过链表方式实现 # 最早了解到这种方法是通过马士兵Java教学视频了解到的方法,其实通过这个方式可以让人更好的理解面向对象的思想和双向循环链表。 第一步,我们要抽象出一些对象,在约瑟夫环这个命题中,有这样两个对象:人和环。人具有几个属性:编号,左边的人和右边的人。环具有几个属性和方法:总人数,第一个人,最后一个人,添加人和移除人。 好了,第二步我们就开始实现自己抽象出来的对象,先是People对象: public class People { private Integer id; //因为是一个圈子那么人就有左右两个属性,而且左右也都是人,所以明确定义其类型 private People left; private People right; public People(Integer i) { this.id = i + 1; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public People getLeft() { return left; } public void setLeft(People left) { this.left = left; } public People getRight() { return right; } public void setRight(People right) { this.right = right; } } 接下来是Circle对象: public class Circle { private int total = 0; private People first = null; private People last = null; /** * 向环里添加人,将新人添加到链表的尾部 * * @param newPeople 新加入都人 */ public void addPeople(People newPeople) { if (total <= 0) { //如果总数小于或等于零,说明这是环里一个人都没有,这是添加后第一个和最后一个都是自己 first = newPeople; last = newPeople; //此时这个人都的左边和右边也都是自己 newPeople.setLeft(newPeople); newPeople.setRight(newPeople); } else { //如果环里有人,则将新人加入到尾部,因为是尾部,所以操作的就是first和last last.setRight(newPeople);//将原先最后一个人的右边变为新人 newPeople.setLeft(last);//将新人的左边设为原最后一个人也就是现在的倒数第二人 newPeople.setRight(first);//将行人的右边设为第一个人 first.setLeft(newPeople);//将第一人的左边变为新人 last = newPeople;//将最有一个人设为新人 } total++; } /** * 删除被淘汰的人 * * @param deletePeople 需要删除的人 */ public void deletePeople(People deletePeople) { if (total <= 0) { return; } else if (total == 1) { //如果环中只有一个人,那么游戏结束,首尾都设为null first = null; last = null; } else { //环中的人大于一个,是开始删除操作 if (deletePeople == first) { first = deletePeople.getRight(); //如果是第一个人,那么他右边的人就变成第一人 } else if (deletePeople == last) { last = deletePeople.getLeft(); //相反,如果是最后一个人,那么他左边的人就变成最后一人 } //将删除的人左边的人的右边 指向 删除的人的右边的人,是不是有点儿绕,举个例子: //注意这是一个环!!! 12345,我们要删除3,那么将2的左边设为4。 deletePeople.getLeft().setRight(deletePeople.getRight()); //同理,将删除的人右边的人的左边 指向 删除人的左边的人。 deletePeople.getRight().setLeft(deletePeople.getLeft()); } total--; } public int getTotal() { return total; } public void setTotal(int total) { this.total = total; } public People getFirst() { return first; } public void setFirst(People first) { this.first = first; } public People getLast() { return last; } public void setLast(People last) { this.last = last; } } 最后,我们开始计算了,先向环里添加人,然后取到第一个人进行处理,处理完第一个之后取到第一人之后的一个人继续处理,最后返回处理完的环。 private static Circle compute(Integer total, Integer keyNumber){ Circle circle = new Circle(); for (int i = 0; i < total; i++) { People people = new People(i); circle.addPeople(people); //向环中添加人 } Integer count = 0;//用来计数 People people = circle.getFirst(); //先拿到第一个人 while (circle.getTotal() > 1) { count++; if (count.equals(keyNumber)) { count = 0; circle.deletePeople(people); } people = people.getRight();//一个一个往后报数 } return circle; } 下面我们来看一下执行结果: public static void main(String[] args) { int total = 10; //定义要添加的人数 int keyNumber = 3; //数到3退出 Circle circle = compute(total, keyNumber); System.out.println(total + "个人围成一圈数数,数到" + keyNumber + "的被淘汰,最后剩下的是第" + circle.getFirst().getId() + "个人。"); } ![141820_KHlV_2826058.png][] 成功,是不是觉得思想上有点绕,但是这是典型的面向对象思想,可以多消化消化。 # 三、Java自带链表实现(LinkedList) # 其实使用Java自带的LinkedList就可以很简单的实现我们要的效果。直接上代码了: package com.csu.test; import java.util.LinkedList; public class Test { public static void main(String[] args) { Integer total = 10; Integer keyNumber = 3; LinkedList<Integer> list = new LinkedList<>(); for (int i = 0; i < total; i++) { list.addLast(i + 1);// 将第一个人,放入链表的第0个位置。 } int index = 0; while (list.size() > 1) { for (int i = 1; i < keyNumber; i++) { if (index == list.size() - 1) { // 查找完这一轮的所有人。 index = 0; } else { index++;// 找到这一轮的第keyNumber个人,准备删除他。 } } list.remove(index);// 该人被淘汰 } System.out.println(total + "个人围成一圈数数,数到" + keyNumber + "的被淘汰,最后剩下的是第" + list.get(0) + "个人。"); } } 是不是很简单,能够这么简单的基础是因为链表的remove()方法的特殊性,第一轮循环时,index=2的元素就是3,但它找到需要删除的元素后链表size-1,此时index=2指向的是原index=3的元素,也就说index不用变,这样正好满足了我们的需求。 约瑟夫环还有其他的方法可以实现,但是我目前学习到的只有这三种方法实现,如果以后学习到其他的方法实现,再回来进行补充。 [https_my.oschina.net_jack90john_blog_1791110_p_1]: https://my.oschina.net/jack90john/blog/1791110?p=1 [123207_rEPC_2826058.png]: https://static.oschina.net/uploads/space/2018/0406/123207_rEPC_2826058.png [141820_KHlV_2826058.png]: https://static.oschina.net/uploads/space/2018/0406/141820_KHlV_2826058.png
相关 约瑟夫环问题 约瑟夫环问题 【问题描述】 有 M 个人,其编号分别为 1-M。这 M 个人按顺序排成一个圈。现在给定一个数 N,从第一个人开始依次报数,数到 N 的人出列,然后又从下一个 末蓝、/ 2022年06月08日 03:19/ 0 赞/ 186 阅读
相关 约瑟夫环问题 list1 = [1 for _ in range(30)] 用1代表活人,0代表死人,先创建一个30个活人的列表 index = 0 索引,用来遍历 青旅半醒/ 2022年05月30日 10:39/ 0 赞/ 159 阅读
相关 约瑟夫环问题 ![这里写图片描述][70] public class Solution { //约瑟夫环问题 public int LastRema 曾经终败给现在/ 2022年05月24日 01:53/ 0 赞/ 191 阅读
相关 约瑟夫环问题 问题描述: N个人围成一圈,从第一个开始报数,第M个将淘汰,退出圈外,重复上述过程n-1次,最后剩下一个,最后留下来的人获胜。求出最后获胜者的编号。 if __n ╰+哭是因爲堅強的太久メ/ 2022年05月17日 01:20/ 0 赞/ 132 阅读
相关 约瑟夫环问题 约瑟夫环问题 / n个人(编号 1...n),先去掉第m个数,然后从m+1个开始报1, 报到k的退出,剩下的人继续从1开始报数.求胜利者的编号. 适用数据范围较小 谁借莪1个温暖的怀抱¢/ 2022年05月15日 02:44/ 0 赞/ 152 阅读
相关 约瑟夫环问题 / 约瑟夫环问题(Josephus) 用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写出C程序。(约瑟夫环问题 Joseph 青旅半醒/ 2022年03月27日 14:54/ 0 赞/ 232 阅读
相关 约瑟夫环问题 先贴着 慢慢研究 : [https://www.cnblogs.com/cmmdc/p/7216726.html][https_www.cnblogs.com_cmmdc_ àì夳堔傛蜴生んèń/ 2022年03月17日 14:14/ 0 赞/ 193 阅读
相关 约瑟夫环问题 转载自[https://my.oschina.net/jack90john/blog/1791110?p=1][https_my.oschina.net_jack90john_ 拼搏现实的明天。/ 2022年03月01日 04:12/ 0 赞/ 218 阅读
相关 约瑟夫环问题 ![ContractedBlock.gif][] ![ExpandedBlockStart.gif][] 1 include <iostream> 2 今天药忘吃喽~/ 2021年12月13日 02:45/ 0 赞/ 277 阅读
相关 约瑟夫环问题 问题描述 1 - n 个人围坐一圈,约定编号为k的人开始报数,数到m的那个人出列,直到所有人出列,由此产生一个队编号的序列 问题分析 1. 先构造一个环形的单向 叁歲伎倆/ 2021年11月01日 22:40/ 0 赞/ 302 阅读
还没有评论,来说两句吧...