现在的位置: 首页 > 综合 > 正文

约瑟夫问题(Josephus problem)

2019年03月21日 ⁄ 综合 ⁄ 共 3124字 ⁄ 字号 评论关闭

文章一:

连接地址:http://blog.csdn.net/Melody_1208/archive/2007/10/01/1809005.aspx

问题的提出:
假设N个人决定选出一个领导,将所有人排成一个圆周,沿着这个圆周每次数M个人就排除对应者,每当有人出列后,剩下的人靠拢,仍然保持一个完整的圆周。问题是找出最后剩下的那个人是谁(根据数学方法,不用这么麻烦,可能早就算出应该是圆周中的哪一个人了)。确定领导的过程是一个N和M的函数,这就是所谓的约瑟夫函数(Josephus  function)。更一般地讲,我们可能希望知道出列人员的顺序。例如,若共有N=9个人,且每数M=5个人就排除对应者,则出列顺序为5  1  7  4  3  6  9  2 ,8为选出的领导。

问题的解答:
问题的解决方式有多种,在此可以构造一个循环链表来解决问题。将N个人按顺序插入一个循环链表,然后从第一个人开始数M个人就删除一个节点,如此往复,直到剩下最后一个节点为止。为此,编写程序如下。
#include <stdlib.h>
#include <stdio.h>

typedef int Item;
typedef struct node *link;
struct node{
    Item item;
    link next;
};

int main(int argc,char *argv[])
{
    int i,N=atoi(argv[1]),M=atoi(argv[2]);
    link t=(link)malloc(sizeof(*t)),x=t;
    t->item=1;t->next=t;
    for(i=2;i<=N;i++){
        x=(x->next=(link)malloc(sizeof(*x)));
        x->item=i;
        x->next=t;
    }
    while(x!=x->next){
        for(i=1;i<M;i++)
            x=x->next;
        x->next=x->next->next;
        N--;
    }
    printf("%d/n",x->item);
    return 0;
}
以上程序的优点在于它能够快速删除节点,若使用数组而不是链表,则会使删除节点的开销增大。使用数组的优点在于它能快速访问数组中任意下标的元素。当我们选择一种数据结构时,必须当心这种选择对算法处理数据的效率的影响。
在C中,指针为链表的抽象概念提供了一种直接而且方便的具体实现,但抽象的基本值并不依赖于任何特定的实现。我们同样可以用整数数组来针对约瑟夫问题实现链表,也就是说,我们可以使用数组索引实现链表,而不是使用指针。下面的表就说明了链表的数组表示方法。具体实现程序有兴趣的话可以编写一下。

  0 1 2 3 4 5 6 7 8
item 1 2 3 4 5 6 7 8 9
next 1 2 3 4 5 6 7 8 0
链表的数组表示

在此,我给出我的实现如下:

#include <stdio.h>
#include <stdlib.h>

struct node{
 int item;
 int next;
};

void main(int argc,char *argv[2])
{
 int i,j,N=/*atoi(argv[1])*/9,M=/*atoi(argv[2])*/5;
 // 构造循环链表
 node *t=(node *)malloc(sizeof node *N);
 for(i=0;i<N;i++)
 {
  t[i].next=i+1;
  t[i].item=i+1;
 }
 t[N-1].next=0;
 i=0 ;
 // 模拟选举过程
 while(t[i].next!=i)  /* 该节点的next域不指向自己则继续循环*/
 {
  for(j=0;j<M-2;j++)
   i=t[i].next;
  t[i].next=t[t[i].next].next; /* 将i的next域值设置为其下下个节点的索引*/
  i=t[i].next;     /* 移到下一个节点*/
 }
 printf("%d",t[i].item);
}

当然,这个实现并不是唯一的,我们还可以分别用两个数组来表示,一个Item数组,一个next数组。大小均为N,然后并不修改Item,只修改next。

问题的反思:
算法的灵魂在于思想,不在实现,我们要掌握的是算法本身,而不是程序。程序只是我们算法的实现,不要拘泥于某一特定语言。语言只是工具而已,选用哪种语言,要看我们要解决的问题的规模,复杂度,时间以及空间的要求。当然,我并不是说不用学语言,而只去学数学。我们要学的是将算法的思想用具体的语言工具表达出来,解决实际遇到的问题。
最后,以上纯属本人愚见,不要当真,不同时期,总会有不同的体会,所以,尽情的享受学习带给你的快乐与困苦吧。

文章二:

连接地址:http://www.charlesgao.com/?p=76

前几天参加了同济大学第五届大学生程序设计竞赛。作为唯一的理学部的队伍,我和我的组员一同去了美丽的嘉定参加决赛。五个小时的比赛的确紧张刺激,结果8道题我们只AC了3道,而第一的两位大牛AC了7道,这也使我看到了与他们之间的巨大差距。虽然我们是学数学的,但目前的数学分析、高等代数和计算机算法并没有多大联系。并且我一直在搞应用,算法这方面确实比较薄弱。不管怎么样,这次也算长了见识,认识了一些朋友,以后还有机会的。

直到这次比赛我才知道同济也有自己的Online Judge(兴奋),我也在TOJ平台上练练手。有很多问题我都无从下手,而且Problem 10005那个WS的问题我至今还没有发现自己哪里错了(只WA一组数据)。今天做Problem 10006,即这个Josephus问题,我差点没晕过去,老是WA。最后才发现是自己犯了思维定势的错误。
Josephus问题:
设有n个人围坐在圆桌周围,按次序编号1,2,3…,n。现在从第1个人开始报数,数到第m的人出列,然后从出列的下一个人开始报数,数到m的人又出列……如此反复,直到所有人全部出列。问最后一个出列的是哪一位?

 

解:
由于这个问题是学习数据结构中链表时的一个典型例子,我以前也曾经做过,于是就又用顺序链表写了个程序,结果好多WA和TLE,后来改成循环链表也是一样。我很困惑,怎么会这样呢?最后才发现,题目只要求输出最后一个出列的人的号码,并没有要求模拟整个过程。我之前做的都是无用功…

对于上述问题,每个人出列后,剩下的人又组成了另一个子问题。只是他们的编号变化了。第一个出列的人肯定是a[1]=m(mod)n(m/n的余数),他除去后剩下的人是a[1]+1,a[1]+2,…,n,1,2,…a[1]-2,a[1]-1,对应的新编号是1,2,3…n-1。设此时某个人的新编号是i,很容易看出,他原来的编号就是(i+a[1])%n。于是,这便形成了一个简单的递归问题。假如我们知道了这个子问题(n-1个人)的解是x,那么原问题(n个人)的解便出来了:(x+m%n)%n=(x+m)%n。问题又有起始条件:如果n=1,那么结果就是1。
程序代码:

#include <iostream>
using namespace std;
int main(){
    
int i,n,m,ans(1); //ans是最后一个出列的人
    
cin>>n>>m; 
    
for (i=2;i<=n;i++) {
        
ans=(ans+m)%i;
        
if (ans==0) ans=i;//遇到0后把它变成i(因为编号是1,2...i)
    
}
    
cout<<ans<<endl;
    
return 0;
}

抱歉!评论已关闭.