引子
据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
进入正题
约瑟夫环问题,在计算机界是一个经典的循环链表问题,题意是:已知 n 个人(分别用编号 1,2,3,…,n 表示)围坐在一张圆桌周围,从编号为 k 的人开始顺时针报数,数到 m 的那个人出列;他的下一个人又从 1 开始,还是顺时针开始报数,数到 m 的那个人又出列;依次重复下去,直到圆桌上剩余一个人。
如图所示,假设此时圆周周围有 5 个人,要求从编号为 3 的人开始顺时针数数,数到 2 的那个人出列:
出列顺序依次为:
编号为 3 的人开始数 1,然后 4 数 2,所以 4 先出列;
4 出列后,从 5 开始数 1,1 数 2,所以 1 出列;
1 出列后,从 2 开始数 1,3 数 2,所以 3 出列;
3 出列后,从 5 开始数 1,2 数 2,所以 2 出列;
最后只剩下 5 自己,所以 5 胜出。
手敲代码实现如下
#include "stdlib.h"
#define OK 1
#define ERROR 0
typedef int ElemType;
typedef int Status;
typedef struct Node{
ElemType data;
struct Node *next;
}Node;
typedef struct Node * LinkList;
/*
1、初始化单向循环链表(带入节点总数)
*/
Status initJosehuList(LinkList *list, int count){
LinkList tempNode,lastNode = NULL;
int i = 1;
while (i <= count) {
//创建一个节点
tempNode = malloc(sizeof(Node));
if (tempNode == NULL) {
return ERROR;
}
tempNode->data = i;
if (*list == NULL) {
//首元节点插入
*list = tempNode;
tempNode->next = tempNode;
}
else {
//尾节点插入
tempNode->next = lastNode->next;
lastNode->next = tempNode;
}
//记录尾节点
lastNode = tempNode;
i++;
}
return OK;
}
/*
2、遍历链表
循环链表的遍历最好用do while语句,因为需要先做一次p = p->next,判断p->next等于首元节点的时候就是找到尾节点了
*/
void ListTraverse(LinkList list){
printf("遍历链表:");
LinkList p = list;
if (!p) {
printf("这是一个空链表");
}
else {
do {
printf("%d ",p->data);
p = p->next;
} while (p != list);
}
printf("\n");
}
int JosehuMethod(LinkList *list,int startIndex,int num){
if (*list == NULL || startIndex < 1 || num < 1) {
return 0;
}
//找到最后面的那个人,因为他是index为1时第一个数数的人的前一个人
LinkList preNode;
for (preNode = *list; preNode->next != *list; preNode = preNode->next);
//记录要出列的人
LinkList locaNode;
//第几次出列
int serialNum = 1;
//记录报到几号了
int newNum = 1;
//判断是否只留下一个人了
while ((*list)->next != *list) {
//1、先找到开始报数的人
if (startIndex > 1) {
preNode = preNode->next;
startIndex --;
continue;
}
//2、找到后,让他开始报数
if (newNum < num) {
newNum ++;
preNode = preNode->next;
}
else {
//3、找到要出列的人,将他出列
newNum = 1;
locaNode = preNode->next;
//判断是否首元节点,要特殊处理下
if (locaNode == *list) {
*list = locaNode->next;
}
preNode->next = locaNode->next;
printf("第%d个出列的人是:%d\n",serialNum,locaNode->data);
serialNum ++;
free(locaNode);
}
}
printf("最后留下的人是:%d\n",(*list)->data);
return (*list)->data;
}
int main(int argc, const char * argv[]){
//初始化
LinkList list;
int count;
printf("输入队列数量:");
scanf("%d",&count);
initJosehuList(&list, count);
//遍历数据
ListTraverse(list);
int startIndex;
int num;
printf("输入开始报数的位置:");
scanf("%d",&startIndex);
printf("输入出列的报数序号:");
scanf("%d",&num);
//执行约瑟夫方法
JosehuMethod(&list, startIndex, num);
printf("\n");
return 0;
}
复制代码
输入效果如下