Algorithm Gossip: 約瑟夫問題(Josephus Problem)

来源:互联网 发布:邀请好友进群js编码 编辑:程序博客网 时间:2024/05/21 21:42

說明

據說著名猶太歷史/數學家約瑟夫(Josephus)有過以下的故事:在羅馬人佔領喬塔帕特後,40個猶太士兵與約瑟夫躲到一個洞中,眼見脫逃無望,一群人決定集體自殺,然而私下約瑟夫與某個傢伙並不贊成,於是約瑟夫建議自殺方式,41個人排成圓圈,由第1個人 開始報數,每報數到3的人就必須自殺,然後由下一個重新報數,直到所有人都自殺身亡為止。約瑟夫與不想自殺的那個人分別排在第16個與第31個位置,於是逃過了這場死亡遊戲。 

解法

只要畫兩個圓圈就可以讓約瑟夫與不想死的傢伙免於死亡遊戲,這兩個圓圈內圈是排列順序,而外圈是自殺順序,如下圖所示: 
約瑟夫問題
使用程式來求解的話,只要將陣列當作環狀來處理就可以了,在陣列中由計數1開始,每找到三個無資料區就填入一個計數,直而計數達41為止,然後將陣列由索引1開始列出,就可以得知每個位置的自殺順序,這就是約瑟夫排列,41個人而報數3的約琴夫排列如下所示:
14 36 1 38 15 2 24 30 3 16 34 4 25 17 5 40 31 6 18 26 7 37 19 8 35 27 9 20 32 10 41 21 11 28 39 12 22 33 13 29 23

由上可知,最後一個自殺的是在第31個位置,而倒數第二自殺的要排在第16個位置。 

如果從另一角度來看,每次有人自殺後,剩下的人重新編號,那問題就可以分解為數個子問題。

如果使用動態規畫,有個遞迴推導公式可在 Josephus problem 找到:
g(1, k) = 0
g(n, k) = (g(n - 1, k) + k) % n

其 中k表示報數k的人就自殺,g(n, k)就是n人組成的圓最後存活者編號,編號是從0開始,由於1人時該人就是存活者,所以g(1, k)為0。簡單來說,如果可以求得n - 1的圓時最後存活者編號x,就可以用來求得n的圓時最後存活者編號(x + k) % n(如果要得到1開始的編號,就對結果加一即可)。

如果使用鏈結串列,問題會更簡單,A串列中是1到n的編號,逐一移出A串列中的編號至B串列,最後A串列為空,B串列就是自殺編號順序,1到n編號所在的索引加一就是約瑟夫排列。

#include <stdio.h> #include <stdlib.h> #define LEN 41#define STEP 3void josephus(int*, int, int);int next(int*, int, int, int);int winner(int, int);int main(void) {     int man[LEN] = {0};     josephus(man, LEN, STEP);    printf("約琴夫排列:");     int i;    for(i = 0; i < LEN; i++) {        printf("%d ", man[i]);     }    printf("\nWinner: %d", winner(LEN, STEP));    return 0; } void josephus(int* man, int len, int k) {    int i, n;    for(i = 1, n = -1; i <= len; i++) {        n = next(man, len, k, n);        man[n] = i;    }}int next(int* man, int len, int k, int n) {    int count = 0;    while(count < k) {        n = (n + 1) % len;        if(man[n] == 0) { count++; }    }        return n;}int winner(int len, int k) {    int g, n;    for(g = 0, n = 1; n <= len; n++) {        g = (g + k) % n;    }    return g + 1;}


#include<stdio.h> #include <stdlib.h>typedef struct node//节点存放一个数据和指向下一个节点的指针{int data;struct node* pnext;} Node;Node *link_create(int n)//创建n个节点的循环链表{//先创建第1个节点Node *p,*q,*head;int i ;p = (Node *)malloc(sizeof(Node));head = p;p->data = 1;for(i = 2;i<=n;i++)//再创建剩余节点{q = (Node *)malloc(sizeof(Node));q->data = i;p->pnext = q;p = q;}p->pnext = head;//最后一个节点指向头部,形成循环链表return head;}void link_process(Node *head,int k,int m)//从编号为k(1<=k<=n)的人开始报数,数到m的那个人出列;{int i;Node *p = head,*tmp1;while(p->data != k)//p指向的节点data为kp = p->pnext;while(p->pnext != p){for(i=1;i<m;i++){tmp1 = p;p = p->pnext;}printf("%d ",p->data);tmp1->pnext= p->pnext;free(p);p = tmp1->pnext;}printf("%d ",p->data);free(p);}int main(){Node *head = link_create(15);link_process(head,1,2);system("pause");return 0;}


原创粉丝点击