趣味算法--约瑟夫环问题

问题描述

已知n个人(以编号1,2,3,...,n分别表示)围坐在一张圆桌上。指定编号为k的人开始从1报数,数到m的那个人出列;出列那个人的下一位又从1开始报数,数到m的那个人出列;以此规则重复下去,直到圆桌上的人全部出列。

分析解决

解决方法主要有逻辑分析、数学分析法。

逻辑分析:就是按照游戏规则一个个报数,报到m的人出局,结构层次简单清晰明了。这种方式实现主要采用顺序表实现

数学分析:采用数学方式归纳统计分析出每次出局人的规律,直接得出每次出局的人,然后以代码实现。这种方法需要较强的数学分析能力,代码较简单。这种实现主要采用数学公式。

逻辑分析

先以C语言数组为例,由于C语言数组不能像Java、Python这种面向对象语言的数组那样使用删除数组元素的方法,每次删除数组中的一个元素,都要将后面元素前移一位,这里简单的将删除的元素值设为0。

假设总人数n=7,从编号k=2的人开始,数到m=3的人出局,下面是分析过程。

 1 #include <stdio.h>
 2
 3 int main(void)
 4 {
 5     int total;      //define total people
 6     int out;        //define out number
 7     int start;      //define start number
 8
 9     /* set the total, out and start number */
10     printf("Please enter the total people\n");
11     scanf("%d", &total);
12     printf("Please enter the out number\n");
13     scanf("%d", &out);
14     printf("Please enter the start position\n");
15     scanf("%d", &start);
16
17     int people[total];        //init the people
18     int i = start - 1;      //init i for people tag
19     int count = 0;          //init count for the people out
20     int remain = total;     //init the remain number of people
21
22     /* init josephus ring */
23     int j = 0;
24     for (j = 0; j < total; j++)
25         people[j] = j + 1;
26
27     /* begin to solve josephus problem */
28     printf("begin to solve josephus‘s problem.\n");
29     /* print josephus ring */
30     for (j = 0; j < total; j++)
31         printf("%d ", people[j]);
32     printf("\n");
33
34     while (1)
35     {
36         if(people[i] > 0)
37         {
38             count++;
39         }else
40         {
41             i++;
42             if (i == total)
43                 i = 0;
44             continue;
45         }
46
47         if(count == out)
48         {
49             printf("The people of %d is out.\n", people[i]);
50             people[i] = 0;
51             count = 0;
52             remain--;
53         }
54
55         i++;
56         if (i == total)
57             i = 0;
58
59         if (remain == 0)
60             break;
61
62     }
63
64     printf("Josephus has solved his problem\n");
65
66     return 0;
67 }

C 数组版

运行后,结果与上面分析一致

下面给出Java的数组对象实现和Python的列表实现。因为Java数组对象和Python列表都有删除元素的方法,这里的逻辑和上面稍有不同。

 1 import java.util.Scanner;
 2 import java.util.ArrayList;
 3 import java.util.List;
 4
 5 public class JOSEPHUS {
 6
 7     public static void main(String[] args) {
 8         /*init data*/
 9         Scanner scanner = new Scanner(System.in);
10         System.out.println("Please enter the total people");
11         int total = scanner.nextInt();
12         System.out.println("Please enter the out number");
13         int out = scanner.nextInt();
14         System.out.println("Please enter the start position");
15         int start = scanner.nextInt();
16
17         int count = 0;        //define count for people
18
19         /*init josephus ring and print it*/
20         List<Integer> people = new ArrayList<Integer>();
21         for (int i = 0; i < total; i++)
22             people.add(i + 1);
23         System.out.println(people);
24
25         int now = start - 1;    //Pointer of people who count
26
27         /*Begin to solve josephus problem*/
28         while(people.size()>0) {
29             count++;
30             if(count == out) {
31                 System.out.println("The people of " + people.get(now) + " is out");
32                 people.remove(now);
33                 //System.out.println(people);
34                 if (now == people.size())
35                     count = 0;
36                 else
37                     count=1;
38             }
39
40             now++;
41             if(now >= people.size())
42                 now = 0;
43         }
44     }
45 }

java数组版

代码运行结果与分析一致,java数组版比C数组版逻辑处理代码稍微简单一点点。

 1 total = int(input("请输入总人数:"))
 2 out = int(input("请输入出局号:"))
 3 start = int(input("请输入开始位置:"))
 4
 5 #初始化约瑟夫环
 6 people = []
 7 for i in range(total):
 8     people.append(i+1)
 9
10 print("需要处理的约瑟夫环为:\n",people)
11 count = 0       #定义报数变量
12 now = start - 1     #now指定当前报数的人
13 while len(people):
14     count += 1
15     if(count == out):
16         print("编号为 %d 的人出局" %people[now])
17         people.pop(now)
18         if now == len(people):
19             count = 0
20         else:
21             count = 1
22     now += 1
23     if now >= len(people):
24         now = 0

Python列表版

运行结果与分析一致,可以看出C、java和Python用数组实现约瑟夫环,Python的代码是最短的,C的代码最长,Java居中。当然代码都还有优化的地方。

也可以使用环形链表实现。

数学分析

总人数有n个人,从k的位置开始报数,报数到m的人出局。

先假设人数n足够大,不管出局多少个人,都不会回到编号为1的位置。

第一次出局的人的位置为k-1+m,出局人的下一位从新开始报数,这就成了一个新的约瑟夫环,相当于第二次报数的起始位置从k+m开始,总人数为n-1;

第二次出局的人的位置为k+m-1+m,出局人的下一位从新开始报数,这就又成了一个新的约瑟夫环,相当于第三次报数的起始位置从k+2m开始,总人数为n-2;

第三次出局的人的位置为k+2m-1+m,出局人的下一位从新开始报数,这就又成了一个新的约瑟夫环,相当于第四次报数的起始位置从k+3m开始,总人数为n-3;

。。。

第t次出局的人的位置为k+tm-1,出局人的下一位从新开始报数,这就又成了一个新的约瑟夫环,相当于第t+1次报数的起始位置从k+tm开始,总人数为n-t。

因为假设人数n足够大,不管出局多少个人,都不会回到编号为1的位置,相当与出局后又成了一个新的约瑟夫环,所以(k+tm-1)%(n-t)=(k+tm-1)为出局人的位置。

现在假设k-1+m>n

当编号n报数时还没有报到m,则从编号1接着编号n报数,相当与n后面还是接着无穷无尽等待报数的人。

假设循环c轮,编号p,报的数是m,编号p要出局,相当于k-1+m=cn+p,即(k-1+m)%cn=p,剩余的人数为n-1。

按上面的方法很容易得出第t次出局的人的编号为(k+tm-1)%(n-t)。

当k-1+m=n时

即编号为n的人报数为m,此时(k+tm-1)%(n-t)=0,重新开始的位置为编号为1的位置。

 1 package yuesefu;
 2 import java.util.ArrayList;
 3 import java.util.List;
 4 import java.util.Scanner;
 5
 6 public class Yue {
 7     public static void main(String[] args) {
 8         Scanner scanner = new Scanner(System.in);
 9         System.out.print("请输入总人数:");
10         int total = scanner.nextInt();
11         System.out.print("请输入出局号:");
12         int count = scanner.nextInt();
13         System.out.print("请输入开始的编号:");
14         int start = scanner.nextInt();
15
16         /*初始化约瑟夫环*/
17         List<Integer> people = new ArrayList<Integer>();
18         for (int i = 1; i <= total; i++) {
19             people.add(i);
20         }
21         int k=start-1;
22         System.out.println("约瑟夫环为:"+people);
23         while (people.size()>0) {
24             k = (k + count)%(people.size())-1;
25             if(k<0) {
26                 System.out.println("出局人的编号为:" + people.get(people.size()-1));
27                 people.remove(people.size()-1);
28                 k=0;
29             }else {
30                 System.out.println("出局人的编号为:" + people.get(k));
31                 people.remove(k);
32             }
33         }
34     }
35 }

Java 实现

 1 total = int(input("请输入总人数:"))
 2 out = int(input("请输入出局号:"))
 3 start = int(input("请输入开始位置:"))
 4
 5 #初始化约瑟夫环
 6 people = []
 7 for i in range(total):
 8     people.append(i+1)
 9
10 print("需要处理的约瑟夫环为:\n",people)
11 now = start - 1     #now指定当前报数的人
12 while len(people):
13     now = (now + out)%len(people)-1
14     if now < 0:
15         print("编号为 %d 的人出局" %people[now])
16         people.pop(now)
17         now = 0
18     else:
19         print("编号为 %d 的人出局" % people[now])
20         people.pop(now)

Python实现

测试的结果也是和前面的测试结果一样的,后面程序处理更简单些,省了很多逻辑判断环节。

原文地址:https://www.cnblogs.com/1-6-0/p/9088324.html

时间: 2024-10-14 13:18:26

趣味算法--约瑟夫环问题的相关文章

基本算法——约瑟夫环问题

关于约瑟夫环问题,我们可以从两种思路去实现,一种是用数组,另一种是采用链表. 用数组方法实现代码: 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #define M 8 5 int find(int *arr, int len); 6 int main(int argc, char* argv[]) 7 { 8 int size = atoi(argv[1]); 9 int* arr

算法:约瑟夫环问题

算法:约瑟夫环问题 [写在前面] 由于本人天生驽钝,所写代码和描述可能不堪入目,高手请移步.但是我一直在努力记录一下有用的知识点,给自己给朋友用,只是希望对大家有帮助. [问题描述] 约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围.从第一个人开始报数,数到m的那个人出列:他的下一个人又从1开始报数,数到m的那个人又出列:依此规律重复下去,求最后一个出圈的人的标号. [代码] void JOSEF(int n,int m) //:n个人

【算法题目】约瑟夫环问题

题目来源:<剑指offer>面试题45 题目:0,1,...,n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字. 解法一:经典解法,用环形链表模拟圆圈.这种方法每删除一个数字需要m步运算,总共有n个数字,因此总的时间复杂度是O(mn).同时这种思路还需要一个辅助链表来模拟圆圈,其空间复杂度是O(n). int LastRemaining(unsigned int n, unsigned int m) { if (n < 1 ||

趣味算法总目录

趣味算法总目录: 排序部分: 归并排序(Merge sort) 堆排序 快速排序 && 希尔排序 && 插入排序 基数排序 排列与组合: 排列 && 组合 网络流问题: 最大流问题 数学问题: 奇妙的数学 1. 约瑟夫(环)问题. 2. 不用任何中间变量交换两个变量 a 和 b 的值. 3. 不用加减乘除求两个整数的和. 4. 不用乘.除.取余运算实现两个 int 值相除. 5. 找数组中单数问题 6. ACM: 今天是星期一,再过 1^1+2^2+3^3+

cdoj525-猴子选大王 (约瑟夫环)

http://acm.uestc.edu.cn/#/problem/show/525 猴子选大王 Time Limit: 3000/1000MS (Java/Others)     Memory Limit: 65535/65535KB (Java/Others) Submit Status 有m个猴子围成一圈,按顺时针编号,分别为1到m.现打算从中选出一个大王.经过协商,决定选大王的规则如下:从第一个开始顺时针报数,报到n的猴子出圈,紧接着从下一个又从1顺时针循环报数,...,如此下去,最后剩

HDU 3089 (快速约瑟夫环)

Josephus again Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 652    Accepted Submission(s): 181 Problem Description In our Jesephus game, we start with n people numbered 1 to n around a circle

约瑟夫环的C语言数组实现

约瑟夫环问题的具体描述是:设有编号为1,2,--,n的n个(n>0)个人围成一个圈,从第1个人开始报数,报到m时停止报数,报m的人出圈,才从他的下一个人起重新报数,报到m时停止报数,报m的出圈,--,如此下去,知道剩余1个人为止.当任意给定n和m后,设计算法求n个人出圈的次序. 一开始看到这这个题目就是觉得这是一个环形的,想到了用链表和用指针,然后看题目的要求是使用数组实现.就先暂时放弃用链表的办法,用数组实现之后再用链表来实现. 一开始的思路是: 1.建立一个长度为n的数组. 2.取出位置编号

C语言数组实现约瑟夫环问题,以及对其进行时间复杂度分析

尝试表达 本人试着去表达约瑟夫环问题:一群人围成一个圈,作这样的一个游戏,选定一个人作起点以及数数的方向,这个人先数1,到下一个人数2,直到数到游戏规则约定那个数的人,比如是3,数到3的那个人就离开这个游戏:按这样的规则,剩下一个人,游戏就结束,这个人就为赢家.(读者可以试着表达,不认同,直接忽略) 抽象分析 这个人就是一个数据个体,数据结点,数据元素.上面产生的数据结构为:单方向循环的链.可以用链表实现,也可以用数组来实现. 链表到数组的迁移 人(数据元素. 数据结点.数据个体) 结点关系 (

约瑟夫环(N个人围桌,C语言,数据结构)

约瑟夫环问题(C语言.数据结构版) 一.问题描述 N个人围城一桌(首位相连),约定从1报数,报到数为k的人出局,然后下一位又从1开始报,以此类推.最后留下的人获胜.(有很多类似问题,如猴子选代王等等,解法都一样) 二.思路分析 (1)可将人的顺序简单编号,从1到N: (2)构造一个循环链表,可以解决首位相连的问题,同时如果将人的编号改为人名或者其他比较方便 (3)将人的编号插入到结构体的Data域: (4)遍历人的编号,输出参与的人的编号: (5)开始报数,从头报数,报到k的人出局(删除次结点)