约瑟夫问题方法总结
n个人围成一个圈,每个人分别标注为1、2、...、n,要求从1号从1开始报数,报到k的人出圈,接着下一个人又从1开始报数,如此循环,直到只剩最后一个人时,该人即为胜利者。例如当n=10,k=4时,依次出列的人分别为4、8、2、7、3、10,9、1、6、5,则5号位置的人为胜利者。给定n个人,请你编程计算出最后胜利者标号数。(要求用单循环链表完成。)
第一行为人数n; 第二行为报数k
10
4
对于约瑟夫问题当前实现方法大概有两种:
一:模拟:
链表模拟:
1 #include<stdio.h>
2 #include<malloc.h>
3 typedef struct List
4 {
5 int data;
6 struct List *next;
7 }LinkList;
8 int main()
9 {
10 LinkList *L,*r,*s;
11 L = (LinkList *)malloc(sizeof(LinkList));
12 r =L;
13 int n,i;
14 int k;
15 scanf("%d%d",&n,&k);
16 for(i = 1;i<=n;i++) ///尾插法建立循环链表
17 {
18 s = (LinkList *)malloc(sizeof(LinkList));
19 s->data = i;
20 r->next = s;
21 r= s;
22 }
23
24 r->next =L->next; //让最后一个链表的下一个节点指向开头
25 LinkList *p;
26 p = L->next;
27
28 while(p->next != p) //开始模拟(判断条件要注意:因为最后肯定剩下一个人, 所以最后一个数据的next还是他本身)
29 {
30 for(i = 1;i<k-1;i++)
31 {
32 p = p->next; //每k个数死一个人
33 }
34 p->next = p->next->next; //将该节点从链表上删除。
35 p = p->next;
36 }
37 printf("%d",p->data);
38 return 0;
39 }
数组模拟:
1 #include<stdio.h>
2 int main()
3 {
4 int n, k;
5 scanf("%d%d", &n, &k);
6 int i;
7 int a[1001];
8 int dead = 0; //表示已经死了多少人
9 int num = 0; //num模拟没有被杀的人的喊数
10 for (i = 1; i<=n; i++) //开始时每个人都可以报数,为了能得到最后一个人的编号,我们让初始值为i下标
11 {
12 a[i] = i;
13 }
14 for (i = 1;; i++)
15 {
16 if (i > n)
17 {
18 i = i%n; //如果大于总人数,我们就从头开始
19 }
20
21 if (a[i] > 0) //如果当前这个人没有死,就报数
22 num++;
23
24 if (k == num && dead != n-1) //如果当前这个人报的数等于k 并且没有已经死亡n-1个人
25 {
26 num = 0;
27 a[i] = 0;
28 dead++;
29 }
30 else if(k == num && dead == n-1) //如果这个人报数等于k,并且已经死了n-1个人,说明当前这个人就是最后的一个活着的了。。
31 {
32 printf("%d", a[i]);
33 break;
34 }
35
36 }
37 return 0;
38 }
二、公式法(即递推):
递推过程:
(1)第一个被删除的数为(m-1)%n;
(2)设第二次的开始数字为k,
做下映射:(即将数字的排列计算还是从0开始)
k--->0
k+1--->1
k+2--->2
--- ---
k-2--->n-2
此时剩下n-1个人 ,假如我们已经知道了n-1个人时,最后胜利者的编号为x,利用映射关系逆推,就可以得出n个人时,胜利者的编号为(x+k)%n(要注意的是这里是按照映射后的序号进行的)
其中k=m%n。
代入
(x+k)%n<=>(x+(m%n))%n<=>(x%n + (m%n)%n)%n<=> (x%n+m%n)%n <=> (x+m)%n
(3)第二个被删除的数为(m-1)%n-1
(4)假设第三轮的开始数字为o,那这n-2个数构成的约瑟夫环为o,o+1,o+2,...,o-3,o-2。
映射
o--->0
o+1--->1
o+2--->2
--- ---
o-2--->n-3
这是一个n-2个人的问题。假设最后胜利者为y,那么n-1个人时,胜利者为(y+o)%(n-1),其中o等于m%(n-1)。代入可得(y+m)%(n-1)
要得到n-1个人问题的解,只需要得到n-2个人问题的解,倒退下去。只有一个人时,胜利者就是编号0.小面给出递推式:
f(1)=0;
f(i)=(f[i-1]+m)%i;(i>1)
这个公式的思想:
现在假设n=10
0 1 2 3 4 5 6 7 8 9
k=3
第一个人出列后的序列为:
0 1 3 4 5 6 7 8 9
即: 3 4 5 6 7 8 9 0 1(1式)
我们把该式转化为: 0 1 2 3 4 5 6 7 8 (2式)
则你会发现: ((2式)+3)%10则转化为(1式)了
也就是说,我们求出9个人中第9次出环的编号,最后进行上面的转换就能得到10个人第10次出环的编号了
设f(n,k,i)为n个人的环,报数为k,第i个人出环的编号,则f(10,3,10)是我们要的结果
当i=1时, f(n,k,i) = (n+k-1)%n
当i!=1时, f(n,k,i)= ( f(n-1,k,i-1)+k )%n
1 #include<stdio.h>
2 int main()
3 {
4 int n, m,i,s=0;
5 scanf("%d%d",&n,&m);
6 for(i=2;i<=n;i++)
7 s=(s+m)%i;
8 printf("%d", s+1);
9 return 0;
10 }
说一下:
for(i=2;i<=n;i++) s=(s+m)%i;
这个式子:
首先从2开始,因为1个人的时候报的数字的人为0号,结果已经确定了。不需要从i=0开始,要注意的是序列从0开始编号的,所以最后的输出结果也要加1.
s表示的是上一轮的结果,m代表是每多少个人出列一次,i代表当前已经出列了多少个人。
整个式子就是根据上一个的出列数和已经出列的人数来算的。
如果还不懂就仔细琢磨哦。
- 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 数组属性和方法
- 玩转注册表,这几个windowsAPI函数就够了
- 施工专题第11篇:Python 包和模块使用总结
- Node.js-具有示例API的基于角色的授权教程
- 删库时,我后悔没早学会的数据库知识
- SwiftUI:使自定义类型符合 Comparable 协议
- 【CCF】线性分类器
- 计数二进制子串
- 6个实例详解如何把if-else代码重构成高质量代码
- leetcode201场周赛
- ASP.NET MVC+LayUI视频上传
- Linux执行tar解压报错tar: Error is not recoverable: exiting now
- elasticSearch学习(四)
- 在美帝的服务器的prefetch和aspera下载比较
- 全面介绍eBPF-概念
- 在ubuntu20上面安装R4