卯未

导航

选队长游戏(简单瑟夫环问题的直观简单数据解法)

        以简单的约瑟夫环问题作为博客生涯的起始吧!

问题描述:

        N个人排成一圈,选定报数起点,从1开始循环报数,报3的人退出,下一个人继续从1开始报数,剩下的最后一个人即为队长。

解决思路:

       这是一个简化的约瑟夫环问题,目前大家主要采用三种方式解决:(1)采用循环链表的方式;(2)采用数组的报计数方式;(3)基于数学原理的递归方式。对于刚入门的人来说,这些方式都不够直观易懂,在此本博客提供一种直观简单的解决思路。

       先看特殊情况:总人数N=1时,队长就是1;总人数N=2时,队长就是2,即总人数N<3时,队长就是排号最后的一位。这将是后续代码的结束部分。

       再看一般情况:总人数N>2时,该约瑟夫环的解决思路可以等效为如下简单直观的方式。每轮把序号是3倍数对应的人员去除,剩下人员重新进行排序,排序的规则如下:原队列中3的最大倍数对应位置后续剩余的人员移动到新队列最前面,原队列中其他剩余人员按原有顺序连续排列到新队列后面,组成的新队列再进行上述操作,直至出现剩余总人数<3,即回到上述特殊情况,从而解决该问题。以总人数为8为例进行如下说明:

初始队列:         1  2  3  4  5  6  7  8

一次处理(新队列): 7  8  1  2  4  5        删除原对列中3号对应人员3和6号对应人员6,7号和8号提前,剩余人员组成新队列

二次处理(新队列): 7  8  2  4           删除原对列中3号对应人员1和6号对应人员5,剩余组成新队列

三次处理(新队列): 4  7  8                                                    删除原队列中3号对应人员2,剩余组成新队列

四次处理(新队列): 4  7                                                              删除原队列中3号对应人员8,剩余组成新队列

五次处理(新队列): 7                                                                        特殊情况处理

       上述解决思路可通过非常简单的通过数组实现,数组索引+1对应上述队列的序号,数组元素对应人员编号(上数队列中个数字)。

代码展示:

 1 import java.util.Scanner;
 2 public class XuanDuiZhangCode {
 3     /**
 4      * 选队长(约瑟夫环)。根据用户输入的队伍人员总数,排定初始人员顺序后,从1到3循环报数,报3的人退出,后续人员继续报数直至选出队长。
 5      */    
 6     public static void main(String[] args) throws InterruptedException {
 7         // 接收用户输入队伍总人数
 8         Scanner input = new Scanner(System.in);
 9         int nums;
10         System.out.println("请输入队伍总人数(>0):");
11         //存储队伍总人数
12         nums = input.nextInt();   
13         // 将队长序号存入captain变量中,给定初始值为-1
14         int captain = -1;         
15         // 按总人数进行队伍的序号排列,排队序列为数组rank
16         int[] rank = new int[nums]; 
17         for (int i = 0; i < rank.length; i++) {
18             rank[i]=i+1;
19         }
20         // 打印原始排队序列
21         System.out.print("原始排列顺序:");
22         for (int i = 0; i < rank.length; i++) {            
23             System.out.print(rank[i]+" ");
24         }
25         System.out.println();
26 
27         //队长序号计算
28         int n = rank.length;    //剩余排队长度,后续计算的中间变量
29         int m = n%3;            //剩余排队长度相对于3的模,后续计算的中间变量
30         int[] temp = new int[nums];     // 去除3倍数后的新排队序列
31         while(true) {
32             // 如果剩余排队序列长度=等于其与3的模,则直接输出队长序号。存在剩余队伍只有1人或2人的两种情况
33             if(n==m) {
34                 captain = rank[m-1];
35                 break;
36             }else {  
37                 //除去上述情况后,删除原序列3的倍数序号对应的元素,剩余元素按如下顺序重新排列:如[1,2,3,4,5],删除3后新序列为[4,5,1,2]
38                 for (int k = 0; k < n-n%3; k++) {
39                     if((k+1)%3==0) {
40                         ;    
41                     }else{
42                         temp[m] = rank[k];
43                         ++m;            
44                     }                
45                 }
46                 // 对原队列中模(含1和2)对应元素进行处理,按顺序存储于temp
47                 for (int t = 0; t < n%3; t++) {
48                     temp[t] = rank[n-n%3+t];
49                 }
50                 // 将排列后的temp赋值给rank,用于下一次循环使用
51                 for (int j = 0; j < temp.length; j++) {
52                     rank[j] = temp[j];
53                 }
54                 n = m;      //新组成的剩余队列长度
55                 m = n%3;    //新组成的剩余队列相对于3的模
56                 // 打印计算过程(中间队列排序)
57                 System.out.print("计算过程(中间排序):");
58                 for (int i = 0; i < n; i++) {            
59                     System.out.print(rank[i]+" ");
60                 }
61                 System.out.println();
62             }
63         }
64         System.out.println("队长是原始排列顺序中的第"+captain+"号");
65     }
66 }

结果展示:

  请输入队伍总人数(>0):
  9
  原始排列顺序:1 2 3 4 5 6 7 8 9
  计算过程(中间排序):1 2 4 5 7 8
  计算过程(中间排序):1 2 5 7
  计算过程(中间排序):7 1 2
  计算过程(中间排序):7 1
  队长是原始排列顺序中的第1号

总结:

      1、只有抓住了问题本质,才能找到针对性的解决思路,有了解决思路才能用程序语言实现。

      2、若延申为更为广泛的约瑟夫问题,则分析思路是相同的。细节的区别在于取模上,本例是用3取模,对应的特殊情况时模为1和2两种,若扩展至用M取模,则对应的特殊情况为1,2,3,······,M-1,这又是一类比较简单的共性问题,可以很容易解决。

posted on 2021-03-06 19:01  卯未  阅读(147)  评论(0编辑  收藏  举报