http://blog.csdn.net/pipisorry/article/details/39271139
问题描述:
约瑟夫生死问题的描述有三:
【其一】据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
【其二】17世纪的法国数学家加斯帕在《数目的游戏问题》中讲了这样一个故事:15个教徒和15个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行直到仅余15个人为止。问怎样排法,才能使每次投入大海的都是非教徒。
【其三】个人,编号,从0开始报数,报到的退出,通常取。剩下的人继续从0开始报数,报到的退出,如此往复。求最终胜利者的编号。
解决算法:
循环链表算法原理:
题目中个人围成一圈,因而启发我们用一个循环的链来表示。可以使用结构数组来构成一个循环链。结构中有两个成员,其一为指向下一个节点的指针,以构成环形的链;其二为该节点在最初环中的序号标记。从第一个节点处开始计数,每数到时,将当前节点删除,表示该人已被扔下海了。这样循环计数直到有最后一个节点为止。
顺序表算法原理:
为了节省空间复杂度,采用线性表来实现。以动态数组元素代替循环链表节点的算法实现。考虑:动态数组的申请、使用、回收三原则。用个元素数组来存放结果,初始化全为1,如果这个人被丢到海里了,就把对应位置的元素置为0;用一个变量依次对数组里不为0的元素计数,数到则把对应位置的数组元素置0。循环控制可以用取余预算实现。
低复杂度算法原理:
无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂度高达O(mn)。我们注意到原问题仅仅是要求出最后的胜利者的序号,而不是要读者模拟整个过程。稍微改变问题描述:n个人,编号0~n-1,从0开始报数,报到m-1的退出,剩下的人继续从0开始报数。求胜利者的编号。
我们知道第一个人,编号一定是(m-1)%n,出列之后,剩下的n - 1个人组成了一个新的约瑟夫环,以编号为k = m%n的人开始,并且从开始报0。
把编号做一下转换:
变换后就完完全全成为了n - 1个人报数的子问题,那么根据上面这个表把这个变回去则刚好就是n个人情况的解。(如果一个人在n-1时的报数为x,则他在n时的报数为x’)
x' = (x + k) % n
如何知道n个人报数的问题的解?只要知道n-1个人的解就行了。n-1个人的解?当然是先求n-2的情况。这显然就是一个倒推问题。
令表示个人玩游戏报退出最后胜利者的编号,最后的结果自然是f[n]。
递推公式
code:
/* 数组算法(有删除元素) O(mn) */
static int jonseArray(int n, int m) {
int *jones = new int[n];
for(int i=0; i<n; i++)
jones[i]=i+1;
int t=0;
for(int left=n; left>=1; left--) { //剩余人数
t=(t+m-1)%left; //将要除去的编号
//printf("%d ", jones[t]);
for(int j=t+1; j<=left-1; j++)
jones[j-1]=jones[j];
}
return jones[0];
}
/* 数组算法(无删除元素) O(mn) */
static int jonseArray2(int n, int m){
int *a = (int *)malloc(sizeof(int) * n);
for(int i = 0; i < n; i++)
a[i] = 1;
int current = 0;
for(int lose_cnt = 0; lose_cnt < n - 1; lose_cnt++){
int call_num = 1; //重新报数
while(call_num < m){
current = (current + 1) % n;
while(a[current] == 0) //跳过已划出的
current = (current + 1) % n;
call_num++;
}
a[current] = 0; //划出一个
current = (current + 1) % n; //current指向下一个
while(a[current] == 0)
current = (current + 1) % n;
}
for(int i = 0; i < n; i++){
if(a[i] == 1)
return i + 1;
}
}
/* 数组算法(无删除元素) O(mn) */
static int jonseArray3(int n,int m){
int a[300];
for(int i=0;i<n;i++)
a[i]=1;
int i = 0, j = 0, k = 0;
while(k!=n-1){
if(a[i]==1){
j=j+1;
if(j==m){
a[i]=0;
k++;
j=0;
}
}
i = (i + 1) % n;
}
for(int i=0;i<n;i++){
if(a[i]==1)
return i + 1;
}
}
/* 链表算法(双循环链表) O(mn) */
typedef struct jonseNode{
int code; //编号(从0开始)
struct jonseNode *next;
struct jonseNode *pre;
}jonseNode;
static int jonseList(int n, int m){
jonseNode *jonseMaxNum; //最大code值的点作为头结点
assert(jonseMaxNum = (jonseNode *)malloc(sizeof(jonseNode)));
jonseMaxNum->code = n - 1;
jonseMaxNum->next = jonseMaxNum;
jonseMaxNum->pre = jonseMaxNum;
for(int i = n - 2; i >= 0; i--){ //头插法插入其它结点
jonseNode * jonseI;
assert(jonseI = (jonseNode *)malloc(sizeof(jonseNode)));
jonseI->code = i;
jonseI->next = jonseMaxNum->next;
jonseMaxNum->next = jonseI;
}
jonseNode *current_pre = jonseMaxNum;
jonseNode *current;
for(int i = 1; i <= n - 1; i++){ //每次除去一个,共除去n-1个
int call_num = 1;
current = current_pre->next;
while(call_num++ < m){
current_pre = current;
current = current->next;
}
current_pre->next = current->next;
//printf("%d\n", current->code);
free(current);
}
return current_pre->code + 1;
}
/* 最优算法(低复杂度算法) */
static int jonseOptimal(int n, int m){
int final = 0;
for(int total = 2; total <= n; total++) //total个人时的winner为final
final = (final + m) % total;
return final + 1;
}
测试:
/****************************************************************************/
/* POJ读书笔记6.1 - 约瑟夫问题 2746 皮皮 2014-9-14 */
/****************************************************************************/
#include <assert.h>
#include <stdio.h>
#include <malloc.h>
#include <string.h>
int main(){
int n, m;
//assert(freopen("simulation\\Jonse.in", "r", stdin));
while(1){
scanf("%d%d", &n, &m);
if(n == 0 && m == 0)
break;
printf("%d\n", jonseArray(n, m));
}
printf("\n");
//assert(freopen("simulation\\Jonse.in", "r", stdin));
while(1){
scanf("%d%d", &n, &m);
if(n == 0 && m == 0)
break;
printf("%d\n", jonseArray2(n, m));
}
printf("\n");
//assert(freopen("simulation\\Jonse.in", "r", stdin));
while(1){
scanf("%d%d", &n, &m);
if(n == 0 && m == 0)
break;
printf("%d\n", jonseArray3(n, m));
}
printf("\n");
//assert(freopen("simulation\\Jonse.in", "r", stdin));
while(1){
scanf("%d%d", &n, &m);
if(n == 0 && m == 0)
break;
printf("%d\n", jonseList(n, m));
}
printf("\n");
assert(freopen("simulation\\Jonse.in", "r", stdin));
while(1){
scanf("%d%d", &n, &m);
if(n == 0 && m == 0)
break;
printf("%d\n", jonseOptimal(n, m));
}
printf("\n");
fclose(stdin);
return 0;
}
测试案例:
5 3
6 2
12 4
8 3
0 0
output:
4
5
1
7
ps:问题可见于 poj2746
from:
http://blog.csdn.net/pipisorry/article/details/39271139