說明
據說著名猶太歷史/數學家約瑟夫(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 3
void 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为k
p = 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;
}