据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
这个问题是计算机和数学中经典的一个计数问题,在算法的中这种类似的问题被叫做与瑟夫环。
与瑟夫问题有很多的解决思路,比如最常见的使用循环链表来解决。
#include <stdio.h> #include <stdlib.h> typedef struct node { int data; struct node *next; }node; node *create(int n) { node *p = NULL, *head; head = (node*)malloc(sizeof (node )); p = head; node *s; int i = 1; if( 0 != n ) { while( i <= n ) { s = (node *)malloc(sizeof (node)); s->data = i++; // 为循环链表初始化,第一个结点为1,第二个结点为2。 p->next = s; p = s; } s->next = head->next; } free(head); return s->next ; } int main() { int n = 41; int m = 3; int i; node *p = create(n); node *temp; m %= n; // m在这里是等于2 while (p != p->next ) { for (i = 1; i < m-1; i++) { p = p->next ; } printf("%d->", p->next->data ); temp = p->next ; //删除第m个节点 p->next = temp->next ; free(temp); p = p->next ; } printf("%d ", p->data ); return 0; }
我最开始见到这个问题,使用递归来解决这个问题。
#include<stdio.h> void josephus(int *a,int n, int i) { int times=1; while(number(a,n)>0) { if(i==n+1) { i=1; } while(a[i-1]==0) { i++; if(i==n+1) { i=1; } } if(times==3) { printf("%d->",a[i-1]); a[i-1]=0; i++; josephus(a,n,i); } times++; i++; } } int number(int *a,int n) { int i=0; int times=0; for(i=0;i<n;i++) { if(a[i]==0) { ; } else { times++; } } return times; } int main() { int i; int n=41; int a[n]; for(i=0;i<41;i++) { a[i]=i+1; } josephus(a,n,1); printf(" "); }
还可以使用数组模仿循环链表来实现。
#include<stdio.h> #include<malloc.h> int main() { int *person,i,node,n,m; scanf("%d%d",&n,&m); person=(int*)malloc(sizeof(int)*(n+1)); for(i=1;i<=n;i++)//初始化圈 { person[i]=i+1;//i表示编号为i的人,person[i]的值表示编号为i的人的下一个人的编号 } person[n]=1;//编号为n的下一个人的编号是1 node=1; while(node!=person[node])//如果某个人的下一个人不是自己,意味着人数超过1人 { for(i=1;i<m-1;i++)//这个循环终止于被杀的人的前一个人 { node=person[node];//下一个人的编号为node,node的值来自于前一个人的person[node] } printf("%d->",person[node]);//输出被杀的人编号 person[node]=person[person[node]];//修改被杀的人的前一个人的person[node]为被杀的人的后一个人的编号 node=person[node];//这句话中的node是被杀的人后一个人 } printf("%d",node);//输出最后幸存者的编号 printf(" "); return 0; }