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

(一) 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为头结点,未建立链表前是空指针
 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;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
约瑟夫问题是一个著名的数学问题,其描述如下:有 n 个人围成一个圆圈,从第 k 个人开始报数,数到第 m 个人出圈,然后从下一个人重新开始报数,数到第 m 个人再出圈,直到所有人都出圈为止。求出最后剩下的人的编号。 下面是一种 C++ 实现的约瑟夫问题解法: ```cpp int josephus(int n, int k, int m) { vector<int> v(n, 0); // 初始化为0 int cur = -1, cnt = 0, t = n; while (t > 0) { cur++; if (cur == n) { cur = 0; } if (v[cur] == 1) { continue; } cnt++; // 计数 if (cnt == m) { cnt = 0; v[cur] = 1; // 出圈 t--; // 剩余人数减1 if (cur == k - 1) { k--; // 重新编号 } } } return k; } ``` 函数 `josephus` 接受三个参数,分别为人数 `n`,开始报数的人的编号 `k`,以及每次报数的上限 `m`。`vector<int> v(n, 0)` 用于记录每个人是否出圈,0 表示未出圈,1 表示已出圈。`cur` 表示当前报数的人的编号,`cnt` 表示当前报数的次数,`t` 表示当前还剩余的人数。 在循环中,每次循环 `cur` 递增,如果 `cur` 超过了 `n`,则将其重置为 0。如果 `v[cur]` 为 1,说明当前这个人已经出圈,需要跳过。否则,`cnt` 加 1。如果 `cnt` 等于 `m`,说明当前这个人需要出圈,将其标记为 1,`t` 减 1,同时将 `cnt` 重置为 0。如果当前出圈的人是编号为 `k-1` 的人,需要将 `k` 减 1,重新编号。最后返回 `k` 即可。 这种解法的时间复杂度为 $O(nm)$,空间复杂度为 $O(n)$。如果希望更高效的解法,可以使用递推公式,时间复杂度可以降为 $O(n)$。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值