引子
據說著名猶太歷史學家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;
}
輸出效果如下
結果.png