<转载>约瑟夫环问题

转载地址:http://blog.csdn.net/qq_24696917/article/details/52198603;

分别用:数组、ArrayList、LinkedList、以及通项公式去解决。 
首先用数组:把人都按顺序放到数组中,每次叫到三的人改变其的值为-1,最后剩下的那个就是编号。

 1 public class Test01 {
 2     public static void main(String[] args) {
 3         int n = 10;//参与游戏的人数
 4         //将数字放入数组
 5         int [] persons=new int[n];
 6         for(int i=1;i<persons.length;i++){
 7             persons[i-1]=i;
 8 
 9         }
10         int saveCount=0;//目前数组中还存在的人数
11         int index=0;//我们要进行报的数
12         while(true){
13             //移除人出局
14 
15             saveCount=0;//记录目前存活的人数每次循环都被初始化
16             for(int i= 0;i<persons.length;i++){
17                 if(persons[i]!=-1){
18 
19                     index++;
20                     if(index==3){
21                         index=0;
22             //当计数器等于3的时候改变计数器的值并且把当前人的值改为-1
23                         persons[i]=-1;
24                     }
25                 }
26                 //如果数组中的值不为-1存活人数+1
27                 if(persons[i]!=-1){
28                     saveCount++;
29                 }
30             }
31             //如果存活的人只剩下一个,退出循环
32             if(saveCount==1){
33                 break;
34             }
35         }
36         System.out.println(Arrays.toString(persons));
37     }
38 }

用数组的方式进行求解是我们思考最少的,其原理就是,我们把初始的编号放到数组中,每次找到要移出的人的时候把这个值改变为-1,直至数组中只有一个不是-1,结束循环。但是这种方法每次循环都要循环整个数组,那我们可不可以每次循环后移出出局的人呢?答案是肯定的,Java为我们提供了很多容器,容器都带有这种功能,下面我们就分别用ArrayList于LinkedList来解决。 
ArrayList:

 1 public class Test02 {
 2 
 3     public static void main(String[] args) {
 4         //这里我用了字母初始化数组,为了更明显的表示
 5         List<String> list = new ArrayList<String>();
 6         for(char i = 'A'; i <= 'A' + 9; i++){
 7             list.add(Character.toString(i));
 8         }
 9 
10         int n = 1;//计数器,因为每次都要移除一个人,所以n = 1
11         while(list.size() > 1){
12             for(int i = 0; i < list.size(); i++){
13                 if(n == 3){
14                     list.set(i, "");//找到叫到三的人把它的值变为空
15                     n = 1;
16                 }else{
17                     n++;
18                 }   
19             }
20             for(int i = list.size() - 1; i >= 0; i--){
21             //每次循环完成之后,统一移除元素,不然会报错
22                 if(list.get(i).length() == 0){
23                     list.remove(i);
24                 }
25             }
26         }
27         System.out.println(list.toString());
28     }
29 }

上面这个ArrayList有一点小瑕疵,因为我们不能每次找到人后就移除此人,而是要找到后先改变它的值,让它为空,然后再每次循环完成后统一移除,那有的人就该问了:那与用数组有什么差别,都是改变元素的值,只不过数组进行判断,ArrayList进行移除。 
事实的确如此,虽然用ArrayList每次循环后都移除了一些元素,下次循环的时候不用循环10次,但还不是我们想要的。所以我们在对这个代码进行改进,如下:

 1 public class Test03 {
 2     public static void main(String[] args) {
 3         List<String> list = new ArrayList<String>();
 4         for(char i = 'A'; i < 'A' + 10; i++){
 5             list.add(Character.toString(i));
 6         }
 7         /*
 8          * 思路:
 9          *  1、得到每一次队列中,最后一个人应该报数的值:int n = list.size % 3
10          *  2、每次找到元素移除后,把计数器的值+1,等于跳过一个人,因为找到人是叫到3的人退出,所以刚移除过的人旁边的人肯定不会移除
11          */
12         int a = 0;
13         System.out.println(list.toString());
14         while(list.size() > 1){
15             for(int i = 0; i < list.size(); i++){
16                 a++;//2
17                 if(a == 3){
18                     if(i == list.size() - 1){
19                         a = 0;
20                     }else{
21                         a = 1;
22                     }
23                     list.remove(i);
24                 }
25             }
26             System.out.println(list.toString());
27         }
28     }
29 }

这种方法可能刚开始没有那么好理解,但是在第二个方法的基础下应该很快能够理解。 
下面就是用LinkedList来解决:

 1 public class Monkey {
 2 
 3     public static void main(String[] args) {
 4         int number = 0;//计数器
 5         int count = 10;//玩家的总数
 6 
 7         LinkedList<Integer> monkeys = new LinkedList<>();
 8         for(int i = 1; i <= count; i++){
 9             monkeys.add(i);
10         }
11 
12         //这里用了迭代器,每次取出数组中的下个元素
13         Iterator<Integer> it = monkeys.iterator();
14         while(count > 1){
15         //每次进行迭代,如果有下个元素,计数器+1
16             if(it.hasNext()){
17                 it.next();
18                 ++number;
19             }else{
20             //如果没有下个元素,迭代器从新赋值,即从头开始
21                 it = monkeys.iterator();
22             }
23             //如果找到元素,把计数器归零,移除元素,总数-1
24             if(number == 3){
25                 number = 0;
26                 it.remove();
27                 count--;
28             }
29         }
30         System.out.println("编号为:" + monkeys.element());
31     }
32 }

LinkedList与ArrayList方法类似,都是找到元素并且移除,都是计数器进行计数每次记录循环结果,当做下次循环开始的计数。

最后介绍一种比较难理解的方法,一种通项公式,因为这是约瑟夫环问题,如果我门改变人数,多往下写,就会找到规律,用高中学到的求解通项公式的方法,可以找到这道题的通项公式 : f(n) = (f(n-1) + k) % n f(1) = 0; 
n代表人数,k代表叫到的数字(本题中:n = 10, k = 3); 
实现代码如下:

 1 public class Test04 {
 2 
 3     public static void main(String[] args){
 4 
 5         Scanner sc = new Scanner(System.in);
 6         System.out.println("请输入参与游戏的人数:");
 7         int number = sc.nextInt();
 8         System.out.println("请输入数到几的人退出游戏");
 9         int k = sc.nextInt();
10         //通项公式   f(n) = (f(n-1) + k) % n
11         int last = 0; // f(1) = 0
12         for(int i = k-1; i <= number; ++i){
13             last = (last + k) % i;
14         }
15         System.out.println(last + 1);
16     }
17 }

使用通项公式很简洁的就解决了此类问题,看起来也简单,但是这一种方法却是最难理解的,因为我们需要自己去找通项公式。

    原文作者:Ascetic
    原文地址: https://www.cnblogs.com/ascetic-xj/articles/6498905.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞