一、约瑟夫环背景介绍
约瑟夫环(Josephus problem)是一个经典的数学问题,讲述了如何在一群人中逐个进行淘汰,直到最后只剩下一个人的问题。故事的背景是:在古代犹太战争中,约瑟夫和他的朋友被罗马军队包围。他们决定宁愿自杀也不成为敌人的奴隶。他们围成一个圆圈,从某个人开始,依次报数。每当报到一个指定的数时,报数的人就会被杀死,直到只剩下一个人。
具体而言,约瑟夫环问题可以描述为:有n个人围成一圈,从第一个人开始报数,报到m的人出局,然后从下一个人开始重新报数,再次报到m的人出局,如此重复,直到只剩下一个人为止。
问题的关键是确定最后剩下的人所在的位置。这个位置称为约瑟夫环的解。
约瑟夫环问题可以用数学递推的方法求解,也可以用循环链表的数据结构进行模拟。数学递推的方法可以通过数学公式直接计算出最后剩下的人所在的位置,而模拟则是通过模拟报数过程,不断删除报到m的人,最后剩下的就是解。
约瑟夫环问题在数学、计算机科学以及算法设计中具有一定的研究价值,它涉及到循环链表、递推关系、递归等概念和算法。
二、约瑟夫环在c语言中的问题形式
编号为 1,2,...,n 的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。开始时任选一个整数作为报数上限值 m,从第一个人起顺时针自1顺序报数,报到 m 时停止,报 m 的人出列,将其密码作为新的m值,从其顺时针方向的下一个人开始重新从1报数;如此下去,直至所有的人全部出列为止。试设计个程序,求出出列顺序。
利用单向循环链表作为存储结构模拟此过程,按照出列顺序打印出各人的编号
三、代码及解题思路
完整代码及完整思路注释如下
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data; //定义每个人所持有的密码
int order; //定义每个人的编号
struct Node* next;
};
int main() {
int n = 0;
int m = 0;
struct Node* head = NULL; //定义头指针
printf("请输入人数n的取值:>");
scanf_s("%d", &n);
printf("请输入报数上限值m:>");
scanf_s("%d", &m);
for (int i = 0; i < n; i++) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
printf("请输入第%d个人的密码:>", i + 1);
scanf_s("%d", &newNode->data); //依次手动输入每个人所持有的密码值(data)
newNode->order = i + 1; //按顺序给每个人编号
newNode->next = NULL;
if (head == NULL) {
head = newNode;
}
else {
struct Node* current = head;
while (current->next != NULL) {
current = current->next;
//每次定义一个新指针,从头开始循环到链表末,之后连接上新节点
//过于麻烦,待修改
}
current->next = newNode;
}
}
struct Node* circule = head;
//类似的手法,从头开始移动到链表末端,之后连接头和尾形成环
while (circule->next != NULL) {
circule = circule->next;
}
circule->next = head;
struct Node* wbh = head; //想不出变量名,用我自己的名字当变量名
int i = 1; //定义指针在循环移动中的移动次数
while (wbh->next != wbh) { //循环结束条件是链表中只剩下一个人没有出列
if (i == m - 1) {
printf("编号为%d的人出列了\n", wbh->next->order);
m = wbh->next->data;
struct Node* temp = wbh->next;
//删除报数为m的人(节点)
wbh->next = wbh->next->next;
free(temp);
i = 0; //重置指针的移动距离,为下次报数做准备
}
i = i + 1;
wbh = wbh->next;
}
//将最后一个未出列的人输出到屏幕上
printf("编号为%d的人出列了\n", wbh->order);
free(wbh);
return 0;
}