迴圈連結串列:約瑟夫問題(非常詳細易理解)
阿新 • • 發佈:2019-02-17
約瑟夫問題來源
據說著名猶太曆史學家 Josephus有過以下的故事:在羅馬人佔領喬塔帕特後,39個猶太人與Josephus及他的朋友躲到一個洞中,39個猶太人決定寧願死也不要被敵人抓到,於是決定了一個自殺方式,41個人排成一個圓圈,由第1個人開始報數,每報數到第3人該人就必須自殺,然後再由下一個重新報數,直到所有人都自殺身亡為止。然而Josephus和他的朋友並不想遵從。首先從一個人開始,越過k-2個人(因為第一個人已經被越過),並殺掉第k個人。接著,再越過k-1個人,並殺掉第k個人。這個過程沿著圓圈一直進行,直到最終只剩下一個人留下,這個人就可以繼續活著。問題是,給定了和,一開始要站在什麼地方才能避免被處決?Josephus 要他的朋友先假裝遵從,他將朋友與自己安排在第16個與第31個位置,於是逃過了這場死亡遊戲。
如何使用迴圈連結串列進行模擬
因為約瑟夫問題時一個環狀,又被稱為約瑟夫環,故用迴圈連結串列非常好進行模擬。
每數2個人後,將第3個人幹掉,在迴圈連結串列中就是把這個節點del掉,然後指標指向後面一個人,數2個人後,又將第3個人幹掉,一直這樣,直到最後不足3個人,依次自殺後退出迴圈!
程式碼中有詳細說明
如果願意,你可以跑一下程式碼。其實就是找規律,方法有很多種,不過迴圈連結串列這種方式很貼近約瑟夫問題也更好理解。
詳細程式碼:
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #define OK 1 #define ERROR 0 typedef struct Node { int data; struct Node* next; } Node; /* 建立迴圈連結串列 在上篇資料結構—迴圈連結串列的實現部落格中迴圈連結串列中有頭結點,在我們的單鏈表中頭結點是非必須,必須要有頭指標,有頭結點頭指標指向頭結點 無頭結點,頭指標指向頭結點。 在迴圈連結串列中,如果有頭結點 那麼尾結點的指標域指向頭結點形成環,如果沒有頭結點 那麼尾結點的指標域指向第一個結點形成環。 有無頭結點都不重要,重要的是這種思想,那種更加方便解決我們的問題,視情況是否需要頭結點。 num 為約瑟夫環的結點個數 建立約瑟夫環的結點,構成約瑟夫環,使用迴圈連結串列模擬 */ int Create(Node** joseph,int num) { if (joseph == NULL || num < 1) { return ERROR; } //建立頭結點 Node* head = (Node*)malloc(sizeof(Node)); head->next = NULL; //移動的指標,開始指向頭結點 Node* p = head; //建立約瑟夫結點,採用尾插法,每次建立的結點放到連結串列尾部 for (size_t i = 1; i <= num; i++) { //建立新結點,並且賦值 Node* node = (Node*)malloc(sizeof(Node)); node->data = i; node->next = head; //下面2步幹啥? //p指向連結串列尾結點,p->next=node 將新建結點新增到連結串列尾部。 //p = node 移動p指標讓其始終指向連結串列尾結點 p->next = node; p = node; } //迴圈結束 p 指向最後一個結點,此時約瑟夫環包含頭結點 //方便解決問題,我們把頭結點去掉,整個環剩下約瑟夫結點。 p->next = head->next; free(head); //約瑟夫環,迴圈連結串列中頭指標指向環第一個結點 *joseph = p->next; return OK; } int Length(Node* joseph) { if (joseph == NULL) { return 0; } Node* target = joseph;//開始target指向第一個結點 int length = 1; for (; target->next!=joseph; target = target->next) { length++; } return length; } /* 模擬約瑟夫自殺問題 每數到第3個人進行自殺,然後從下一個人重新開始數,數到第3個人進行自殺... 如果是4人個進行自殺 1 2 3 4 死亡順序:->3->2->4->1 n 為 數到第 n個人自殺,約瑟夫 問題 是 每數到第3個人自殺。 這裡可以進行設定,也可以每數到 第4個人進行自殺。 */ int ShowKill(Node* joseph,int n) { if (joseph == NULL) { return ERROR; } int num = Length(joseph); //skip為殺第n個人要跨越的人數 int skip = n-1; Node* p = joseph; Node* begin = p;//begin指向開始數的第1個人 while (1) { begin = p; //找到自殺人的前一個位置 for (size_t i = 0; i < skip-1; i++) { p = p->next; } //最後剩2個人時,依次自殺 //temp == p->next 時剩下最後2個人! if (begin == p->next) { printf("->%d", begin->data); printf("->%d", begin->next->data); free(begin); free(p); begin = NULL; p = NULL; break; } //迴圈完,p 指向自殺前面一個人 //p->next 進行自殺,實質上就是將p->next結點從迴圈連結串列中幹掉 Node* kill = p->next; printf("-->%d", kill->data); //將kill移除結點 p->next = kill->next; free(kill); kill = NULL; //p移動到後面3個人的第1個人的位置 p = p->next; } printf("\n"); return OK; } int main(int argc, char *argv[]) { Node* joseph = NULL; int num = 1; while (num) { printf("請輸入約瑟夫環人數(輸入0退出):"); scanf("%d", &num); if (!num) { break; } printf("約瑟夫環死亡順序:\n"); joseph = NULL; Create(&joseph,num); ShowKill(joseph, 3); } return 0; }
驗證結果截圖: