(一) O(n)时间的解法
只能解出最后一个出局的人,也就是最后留下的人——默认是从编号为1的人开始数起
n个人在一个圆桌上吃饭,每m个人杀掉一个,直到最后剩下一个人。
问最后剩下哪个人?
将人分别标记为0,1,2,...,n-1,得到n的一个完全剩余系。
可得如下递推公式:
/- (J(n-1,m)+m+1) mod n, 当n>1
J(n,m)=| , n,m∈Z
/- 0, 当n=1
今天数据结构课在讲这个东西的链表模拟解法。我就开始想数论解法。
这个问题以前困惑过我很久,今天终于想到了用李昌勇教的递归方法来解。
下附网上搜到的一个证明:
设n个人围成一圈,标号为0..n-1,从第一个人开始依次从1到k循环报数,当报到k的时候此人出圈。设J(n, k, i)表示第i个出圈的人的标号。
定理一:
J(n, k, 1) = (k-1) mod n, (n >= 1, k >= 1) ………… (1)
证明:
由定义直接得证。Q.E.D.
定理二:
J(n+1,k, i+1) = (k + J(n, k, i)) mod (n+1), (n >= 1, k >= 1, 1<= i <= n) ………… (2)
证明:
设J(n, k, i) = g,因此如果有n个人,从0开始报号,第i个出圈的标号为g。现在考虑J(n+1, k,i+1),因为J(n+1, k, 1) = (k-1) mod (n+1),即第一步的时候删除数字(k-1) mod (n+1),第二步的时候从数字k开始数起。因而问题变为了找到剩下的n个数字中从k开始数起被删除的第i个数字(注意这时(k-1) mod (n+1)已经被删除了),而这恰好就是(g+k) mod (n+1),(2)成立。 Q.E.D.
根据(2),很容易求得n个数里面第i个出圈的数。
对于k = 2, 3的情况,直接可以推导出公式来。但是对于k>=4的情况,还没有推导出公式来,目前最好的算法是一个根据估计J(n, k, i)上下界的快速算法。
更具体的分析,参见
[1] Lorenz Halbeisen, The Josephus Problem, 1994
[2] Woodhouse, D. , The extended Josephus problem, Rev.Mat. Hisp.-Amer.(4) 33 (1973), 207-218
[3] Robinson, W. J.,The Josephus problem, Math. Gazette 44 (1960), 47-52
[4] Jakobczyk, F. , On the generalized Josephus problem, Glasgow Math.J.14(1973), 168-173
以及一个程序:
//函数接收n和m(n个人在一个圆桌上吃饭,每m个人杀掉一个),返回最后出圈的是第几个人
/*e.g. yuesefu(5,2)=3
yuesefu(2,100)=1*/
int yuesefu(int n,int m) //默认是从编号为1的人开始数起
{
int i,r=0;
for (i=2;i<=n;i++) r=(r+m)%i;
return r+1;
}
总结:这个算法的时间复杂度为O(n),相对于模拟算法已经有了很大的提高。算n,m等于一百万,
一千万的情况不是问题了。可见,适当地运用数学策略,不仅可以让编程变得简单,而且往
往会成倍地提高算法执行效率。
或参见:http://blog.csdn.net/lvroyce/archive/2009/02/13/3883760.aspx
(二) 一般解法
#include <iostream>
using namespace std;
//n是人数(编号1,2,……,x),m是出列号,k是起始人编号
void Josefus(int n,int m,int k,int a[])
{
int j=0, l=0;
while (l<=n)
{
for (int i=1;i<=n;i++)
{
if (a[i]==1)
{
j++;
if (j==m)
{//满足出列号
a[i]=0;
if (i==n&&k>1)
{
cout<<1<<endl;
}
else
{
cout<<i+(k-1)<<endl;
}
j=0;
l++;
}
}
}
}
}
int main()
{
int n,m,k;
cout<<"请输入参数: n--总人数,m--每m个人出局,k--起始人编号: ";
cin>>n>>m>>k;
int *a = new int[n];
for(int i=1;i<=n;i++)
a[i] = 1;
Josefus(n,m,k,a);
delete []a;
return 0;
}
(三)链表解法
#include <iostream.h>
#include <stdlib.h>
typedef struct Node
{
int data;
struct Node* next;
}LNode, *LinkList;
//n为总人数,m为出列者喊到的数,k为第一个开始报数的人编号
void JosephRing(int n, int m, int k)
{
LinkList p, r; // p为当前结点,r为辅助结点,指向p的前驱结点
LinkList list = NULL; //list为头结点,未建立链表前是空指针
for(int i = 1; i <= n; i++) //建立循环队列
{
p = (LinkList)malloc(sizeof(LNode));
p->data = i;
if(list == NULL)
list = p;
else
r->next = p;
r = p; //建立链表中的每个节点
}
p->next = list; //建立好使队列循环起来
p = list; //使p指向头节点
//把当前指针移动到第一个报数的人
for(i = 1; i < k; i++)
{
r = p;
p = p->next;
}
//循环地删除队列结点
while(p->next != p)
{
for(i = 1; i < m; i++)
{
r = p;
p = p->next;
}
r->next=p->next;
cout<<p->data<<endl;
free(p);
p=r->next;
}
cout<<endl<<"最终剩下的人为: "<<p->data<<endl;
}
int main()
{
int n,m,k;
cout<<"请输入参数: n--总人数,m--每m个人出局,k--起始人编号: "<<endl;
cout<<"要求: n>0; 1=<k<=n."<<endl;
cin>>n>>m>>k;
cout<<"出队顺序如下: "<<endl;
JosephRing(n, m, k);
system("pause");
return 0;
}
#include <iostream>
using namespace std;
void yuesefu(int a[],int n,int m,int s)
{
int k = n; //总数
int i = m-1+s-1;
int c = 0,p;
while(k > 1)
{
i%=n;
cout<<"第"<<++c<<"个是:"<<a[i]<<endl;
a[i] = 0;
k--;
p = 1;
while(p<=m)
{
i++;
if(a[i%n] != 0)
p++;
}
}
for(int i=0;i<n;i++)
if(a[i] != 0)
{
cout<<"最后剩下的是:"<<a[i]<<endl;
break;
}
}
int main()
{
int n,m,k;
cout<<"n(总数)、m(报数)、k(起始人):";
cin>>n>>m>>k;
int *a = new int[n];
for(int i=0;i<n;i++)
a[i] = i+1;
yuesefu(a,n,m,k);
delete []a;
a = 0;
return 0;
}