现在的位置: 首页 > 综合 > 正文

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

2013年10月03日 ⁄ 综合 ⁄ 共 2127字 ⁄ 字号 评论关闭

說明

據說著名猶太歷史/數學家約瑟夫(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;
}
【上篇】
【下篇】

抱歉!评论已关闭.