选队长游戏(简单瑟夫环问题的直观简单数据解法)
以简单的约瑟夫环问题作为博客生涯的起始吧!
问题描述:
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,这又是一类比较简单的共性问题,可以很容易解决。