(一) 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
今天資料結構課在講這個東西的鏈表類比解法。我就開始想數論解法。
這個問題以前困惑過我很久,今天終於想到了用李昌勇教的遞迴方法來解。
下附網上搜到的一個證明:
這就是Josephus問題
設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為頭結點,未建立鏈表前是null 指標
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;
}