Question: 约瑟夫问题的实现
n个人围成一个圈,每个人分别标注为1、2、...、n,要求从1号从1开始报数,报到k的人出圈,接着下一个人又从1开始报数,如此循环,直到只剩最后一个人时,该人即为胜利者。例如当n=10,k=4时,依次出列的人分别为4、8、2、7、3、10,9、1、6、5,则5号位置的人为胜利者。给定n个人,请你编程计算出最后胜利者标号数。(要求用单循环链表完成。)
题目就是这样了,要求输入两个整数,输出胜利者(最后一个出圈)。题目要求用链表完成,除了链表方法外,还有数组和数学公式来解决,一共三种。
首先是最常见的数组算法,首先定义一个一维整形数组a[size],并初始化为0,作为模拟所有人以及是否出圈;用一个整形数作为报数count的编号,当等于k时,初始化;用一个整形数m表示剩下的人数。算法原理如下:当count等于k时,将a[i](i是当前报数人的编号)赋值为1,并初始化count,剩余人数m自减1,报数继续。当遇到a[i]等于1时,直接跳过,让下一个人报当前的数。当m等于1时,表示只剩下最后一个人,报数结束。
操作如下
关键代码如下(C):
#include<stdio.h>
#define size 1001
int main(){
int n = 10;
int k = 4;
int a[size] = {0};
printf("%d", a[0]);
scanf("%d", &n);
scanf("%d", &k);
int flag = 0; //记录最后一个人
int m = n;
int s = 0;
while(m >= 1){
for(int i = 1; i <= n; i++){
if(a[i] != 1){
s++;
}else{
continue;
}
if(s % k == 0){
// printf("%d\n", i); //查看每次出圈的人
if(m == 1){
flag = i;
break;
}
s = 0;
a[i] = 1;
m--;
}
}
}
printf("%d", flag);
return 0;
}
接下来就是对数学算法的探究,数组是直接改变“报数人”的状态,而这个算法是改变“报数人”逻辑上的连接关系。当“报数人”出圈后,将下一个人的编号改为0,然后依次编号,当只剩最后一个人时,他的编号就是0,通过倒推,可以得知这个人的编号,因为是从0开始编号的,所以这个人的实际编号应加一。但是我们只是只是求最后一个人的编号,而不是如何所有的“报数人”出圈只剩一个人,这是没必要的。
“报数人”出圈大致如下:
如图所示,n表示总人数,k表示报数的最大值,i表示剩余人的总数,a表示最后一个人的编号。可以看到i=5的那一行,当编号为2时,则出圈。那么,为了重新构建新的序列,3是如何变为0的呢?不难想到,因为k=3,所以序号为3的要变为0;而0呢,是如何变为2的?因为0小于k,所以0 - k = -3,序数为负数,显然不对。因此,但h(当前报数人的编号) - k 小于0时,应该与当前人数i相加。因为要判断差是否小于零,可以事先让所有的小于k的编号都加上当前总人数i,在进行求余,就可以得到下一轮的新编号(h = h > k? h: h + i;)。那么,这样就可以实现报数人出圈了。但问题是如何求得最后一个报数人的实际编号呢?
同理,根据报数人出圈的规律,可以的知最后一个人的编号一定为0,反推就可以得到比实际编号小一的编号。就可以得到公式a = (a + k) % i。
算法具体实现如下:
#include<stdio.h>
int main(){
int n = 10;
int k = 4;
scanf("%d", &n);
scanf("%d", &k);
int a = 0;
for(int i = 1; i <= n; i++){
a = (a + k) % i;
}
printf("%d", a + 1);
return 0;
}
参考博文:https://blog.youkuaiyun.com/yanweibujian/article/details/50876631
最后就是链表实现,链表直接将报数人删除,十分的形象。
用链表实现,就得先建立,用结构体存储编号的连接下一个节点。便于操作,建立一个循环链表,采用头插法存储编号。
当报数到k时,获取上一个节点,把上一个节点的指针指向当前节点的下一个节点,然后free当前节点。当只剩下最后一个节点时,取出节点的数据,这就是最后一个报数人。
算法具体实现如下:
#include<stdio.h>
#include<stdlib.h>
struct List{
int data;
struct List *next;
};
void createList(struct List *&head, int n){
struct List *p1, *p2;
p1 = head = (struct List *)malloc(sizeof(struct List));
for(int i = 1; i <= n; i++){
p2 = (struct List *)malloc(sizeof(struct List));
p2->data = i;
if(i == 1){
head = p2;
head->next = NULL;
p1 = head;
}else{
p1->next = p2;
p1 = p2;
}
}
p1->next = head;//首尾相连
}
//void testOut(struct List *head){//输出所有节点内容
// struct List *p;
// p = head;
// do{
// printf("%d\n", p->data);
// p = p->next;
// }while(p != head);
//}
void deal(struct List *head, int m, int n){
int count = 0;
struct List *p;
p = head;
while(head->next != p){
head = head->next;
}
p = head;
head = head->next;
while(n != 1){
count++;
if(count % m == 0){
struct List *k;
k = head;
// printf("%d\n", head->data);//输出每一次出圈人
head = head->next;
p->next = head;
n--;
free(k);
}else{
p = p->next;
head = head->next;
}
}
printf("%d", head->data);
free(head);
}
int main(){
struct List *head, *p;
int n = 10;
int m = 4;
scanf("%d", &n);
scanf("%d", &m);
createList(head, n);
deal(head, m, n);
return 0;
}