算法科普:什么是约瑟夫环
1 问题描述
约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知 n 个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为 k 的人开始报数,数到 m 的那个人出圈;他的下一个人又从 1 开始报数,数到 m 的那个人又出圈;依此规律重复下去,直到剩余最后一个胜利者。
例如:有10个人围成一圈进行此游戏,每个人编号为 1-10 。若规定数到 3 的人出圈。则游戏过程如下。
(1)开始报数,第一个数到 3 的人为 3 号,3 号出圈。 1, 2, 【3】, 4, 5, 6, 7, 8, 9, 10。 (2)从4号重新从1开始计数,则接下来数到3的人为6号,6号出圈。 1, 2, 【3】, 4, 5, 【6】, 7, 8, 9, 10。 (3)从7号重新从1开始计数,则接下来数到3的人为9号,9号出圈。 1, 2, 【3】, 4, 5, 【6】, 7, 8, 【9】, 10。 (4)从10号重新从1开始计数,由于10个人称环形结构,则接下来数到3的人为2号,2号出圈。 1, 【2】, 【3】, 4, 5, 【6】, 7, 8, 【9】, 10。 (5)从4号重新从1开始计数,则接下来数到3的人为7号,7号出圈。 1, 【2】, 【3】, 4, 5, 【6】, 【7】, 8, 【9】, 10。 (6)从8号重新从1开始计数,则接下来数到3的人为1号,1号出圈。 【1】, 【2】, 【3】, 4, 5, 【6】, 【7】, 8, 【9】, 10。 (7)从4号重新从1开始计数,则接下来数到3的人为8号,8号出圈。 【1】, 【2】, 【3】, 4, 5, 【6】, 【7】, 【8】, 【9】, 10。 (8)从10号重新从1开始计数,则接下来数到3的人为5号,5号出圈。 【1】, 【2】, 【3】, 4, 【5】, 【6】, 【7】, 【8】, 【9】, 10。 (9)从10号重新从1开始计数,则接下来数到3的人为10号,10号出圈。 【1】, 【2】, 【3】, 4, 【5】, 【6】, 【7】, 【8】, 【9】, 【10】。 (10)最终剩余 4 号,4 号为胜利者。
2 数组求解
2.1 解题思想
用数组求解的基本思想就是用一个一维数组去标识这 n 个人的状态,默认全为 1 ,也就是都在圈子内,当数到 m的人出圈之后,标识置为 0(就是出圈了),同时报数器清 0,下一个人要从 1 开始。在每次报数之前要判断他是否在圈子内(也就是他的标识是否为 1 ),如果在圈子里面才会继续报数。定义一个变量记录出圈的人数, 出圈的人数等于 n-1 时,则游戏结束。
2.2 代码实现
#include<stdio.h>
#define N 1000000 //记录玩游戏最大人数
int flag【N】 = {0};
int main()
{
int n = 0, m = 0;
scanf("%d%d", &n, &m);//输入玩游戏人数和计数m
int i = 0;
int count = 0; //记录已经出圈的人数
int num = 0; //报数器
for(i = 1; i <= n; i++) {
flag【i】 = 1;//所有人入圈
}
while(count < n - 1) {
for(i = 1; i <= n; i++ ) {
if (1 == flag【i】) {//在未出圈的人数中计数
num++;
if(num == m) {//此人数到m则出圈
printf("%dn", i);
count++;//出圈人数加1
flag【i】 = 0;//出圈
num = 0;
}
if(count == n - 1) {
break;
}
}
}
}
for(i = 1; i <= n; i++) {
if(1 == flag【i】) {//输出赢家,标识为1
printf("The last one is : %dn", i);
}
}
return 0;
}
3 循环链表求解
3.1 解题思想
约瑟夫环问题可以转化为循环链表的数据结构来求解。可以将每个人看做链表的单个节点,每个节点之间通过链表的 next 指针连接起来,并且将链表末尾节点指向头节点就形成的环,由链表构成的环形结构在数据结构中称为循环链表。
3.2 代码实现
#include <stdio.h>
#include <stdlib.h>
/*声明一个链表节点*/
typedef struct node
{
int number;//数据域,存储编号数值
struct node *next;//指针域,指向下一个节点
}Node;
/*创建链表节点的函数*/
Node* CreatNode(int x)
{
Node *p;
p = (Node*)malloc(sizeof(Node));
p->number = x;//将链表节点的数据域赋值为编号
p->next = NULL;
return p;
}
/*创建环形链表,存放整数1到n*/
Node* CreatJoseph(int n)
{
Node *head,*p,*q;
int i;
for(i = 1; i <= n; i++)
{
p = CreatNode(i);//创建链表节点,并完成赋值
if(i == 1)//如果是头结点
head = p;
else//不是头结点,则指向下一个节点
q->next = p;
q = p;
}
q->next = head;//末尾节点指向头结点,构成循环链表
return head;
}
/*模拟运行约瑟夫环,每数到一个数,将它从环形链表中删除,并打印出来*/
void RunJoseph(int n,int m)
{
Node *p,*q;
p = CreatJoseph(n);//创建循环链表形式的约瑟夫环
int i;
while(p->next != p)//循环条件,当前链表数目大于1
{
for(i = 1; i < m-1; i++)//开始计数
{
p = p->next;
}
//第m个人出圈
q = p->next;
p->next = q->next;
p = p->next;
printf("%d--",q->number);//输出出圈的序号
free(q);
}
printf("n最后剩下的数为:%dn",p->number);
}
int main()
{
int n,m;
scanf("%d %d",&n,&m);
RunJoseph(n,m);
return 0;
}
4 递推公式求解
4.1 解题思想
约瑟夫环中,每当有一个人出圈,出圈的人的下一个人成为新的环的头,相当于把数组向前移动 m 位。若已知 n-1 个人时,胜利者的下标位置位 f(n−1,m) ,则 n 个人的时候,就是往后移动 m 位,(因为有可能数组越界,超过的部分会被接到头上,所以还要模 n ),根据此推导过程得到的计算公式为: f(n,m) = (f(n−1,m) + m) % n。 其中,f(n,m) 表示 n 个人进行报数时,每报到 m 时杀掉那个人,最终的编号,f(n−1,m) 表示,n-1 个人报数,每报到 m 时杀掉那个人,最终胜利者的编号。有了递推公式后即可使用递归的方式实现。
4.2 递归代码实现
#include <stdio.h>
int Joseph(int n,int m)/*计算约瑟夫环的递归函数*/
{
if(n <= 1 || m <= 1)//设置游戏人数限定值
return -1;
if(n == 2)//设置边界值
{
if(m % 2 == 0)
return 1;
else
return 2;
}
else
{
return (Joseph(n-1,m) + m-1) % n+1;//递归调用
}
}
int main()
{
int n,m,x;
scanf("%d %d",&n,&m);
x=Joseph(n,m);
printf("最后一个数为:%dn",x);
return 0;
}
4.3 迭代代码实现
#include <stdio.h>
/*计算约瑟夫环问题的迭代法函数*/
int Joseph(int n,int m)
{
int i;
int x,y;
if(n <= 1 || m <= 1)
return -1;
if(m % 2 == 0)
y = 1;
else
y = 2;
for(i = 3; i <= n; i++)
{
x = (y-1 + m) % i + 1;
y = x;
}
return y;
}
int main()
{
int n,m,x;
scanf("%d %d",&n,&m);
x = Joseph(n,m);
printf("最后一个的编号是:%dn",x);
return 0;
}
实际应用
比如你们公司需要团建,你可以设计这个游戏,根据游戏人数的多少,将你和你心仪的妹子安排在合适的座位上,一同进最后的决赛圈~
- go sync.Mutex 设计思想与演化过程 --转
- 漏洞预警 | Ubuntu 16.04版本存在本地提权漏洞(附EXP)
- 通过“震网三代”和Siemens PLC 0day漏洞,实现对工控系统的入侵实验
- 安卓端渗透工具DVHMA:自带漏洞的混合模式APP
- 小萝莉说Crash(二): Unrecognized selector xxx 之 ForwardInvocation
- 5分钟教程:如何通过UART获得root权限
- 源码级剖析PHP 7.2.x GD拒绝服务漏洞
- 美女程序媛发福利,读懂ANR的trace文件So easy
- Openshift高阶探索实验
- 卡卡卡!小萝莉告诉你开发iOS应用如何避免卡顿
- Bugly即将支持的ANR,精神哥告诉你是个什么鬼?
- Go语言Goroutine与Channel内存模型
- 手把手教 | 深度学习库PyTorch(附代码)
- 如何定位Obj-C野指针随机Crash(一):先提高野指针Crash率
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Java中多线程的使用(超级超级详细)+多线程的实现原理 2
- protobuffer的前世今生(三)——序列化和反序列化性能比较
- 女生勿扰,只适合男孩子的 Python 爬虫,里面东西不给钱统统白送
- LTP 4来了!
- protobuffer的前世今生(五)——在Java中的两种使用方式之注解模式
- Java File类基础解析 1
- jit即使编译语言调用c++
- java JDBC系列1 JDBC类的简析与JDBC的基础操作
- TRTC Android端开发接入学习之常见问题(十一)
- 树莓派基础实验7:倾斜开关实验
- Hive SQL经典优化案例
- 树莓派基础实验8:振动开关实验
- 几行代码实现cookie的盗取
- 从apollo的初始化看spring boot 1.5.3启动过程( 一)
- CSRF(跨站请求伪造)学习总结