约瑟夫问题:设有n个人围成一圈,每个人的编号依次为1,2,...,n。现从编号为k的人开始报数,数到m的人便出列,接着从出列的下一个人重新开始报数,数到m的人又出列,以此类推,直到所有人都出列为止。现要求该n个人的出列顺序,设为函数f(n,m,i)。
算法分析:第一种情况,假设有10人,数到3出列。开始给这10个人编号:0,1,2,3,4,5,6,7,8,9。
第一次:编号为2 的人出列:0 1 3 4 5 6 7 8 9,出列的人为(m-1)%10=f(10,3,1);
出列后变为10-1=9人的约瑟夫问题,从编号3开始数:
3 4 5 6 7 8 9 0 1 (1)
0 1 2 3 4 5 6 7 8(2)
可以发现((2)+3)%10=(1);序列f(10,3,2)=f(9,3,1)+3;
第二次:编号为5 的人出列:3 4 6 7 8 9 0 1 (1),出列的人为f(10,3,2)=f(9,3,1)+3;
相对于之前的9人约瑟夫问题来说序列变为:
3 4 6 7 8 9 0 1 (3)
0 1 3 4 5 6 7 8 (4)
出列后变为8人的约瑟夫问题,从编号6开始数:
6 7 8 9 0 1 3 4 (5)
3 4 5 6 7 8 0 1 (6)
0 1 2 3 4 5 6 7 (7)
可以发现((7)+3)%9=(6);((6)+3)%10=(5);序列f(10,3,3)={[f(8,3,1)+3]%9+3}%10={f(9,3,2)+3}%10;
依次类推,f(n,m,i)函数求第i次输出的人的编号;
f(n,m,i)=(m-1)%n;(n=1),
f(n,m,i)=[f(n-1,m,i-1)+m]%n;(n!=1),
具体的代码实现,如下:
当依次输出出列人的编号:
#include <stdio.h>
#include <stdlib.h>
int fun(int m,int k,int i){
if(i==1)
return (m+k-1)%m;
else
return (fun(m-1,k,i-1)+k)%m;
}
int main(int argc, char* argv[])
{
for(int i=1;i<=10;i++)
printf("第%2d次出环:%2d\n",i,fun(10,3,i));
return 0;
}
当输出留到最后的人的编号:
#include <stdio.h>
#include <stdlib.h>
int fun(int m,int k,int i){
if(i==1)
return (m+k-1)%m;
else
return (fun(m-1,k,i-1)+k)%m;
}
int main(int argc, char* argv[])
{
// for(int i=1;i<=10;i++)
printf("最后出列的是:%2d\n",fun(10,3,10));
return 0;
}