您的位置:首页 > 理论基础 > 数据结构算法

约瑟夫问题(Josephus问题)的递推O(n)解法、循环解法、单循环链表解法

2010-01-12 16:38 495 查看
(一) 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
;
 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
;
 for(int i=0;i<n;i++)
  a[i] = i+1;
 yuesefu(a,n,m,k);
 delete []a;
 a = 0;
 return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息