约瑟夫问题(Josephus problem)

文章一:

连接地址: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中,指针为链表的抽象概念提供了一种直接而且方便的具体实现,但抽象的基本值并不依赖于任何特定的实现。我们同样可以用整数数组来针对约瑟夫问题实现链表,也就是说,我们可以使用数组索引实现链表,而不是使用指针。下面的表就说明了链表的数组表示方法。具体实现程序有兴趣的话可以编写一下。

 012345678
item123456789
next123456780
链表的数组表示

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

#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 ;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
背景   约瑟夫问题Josephus Problem)据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。 然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。   原题:  用户输入M,N值,N个人围成一个环,从0号人开始数,数到M,那个人就退出游戏,直到最后一个人 求最后一个剩下的人是几号? 问题描述   设编号为1-n的n(n>0)个人按顺时针方向围成一圈.首先第1个人从1开始顺时针报数.报m的人(m 为正整数).令其出列。然后再从他的下一个人开始,重新从1顺时针报数,报m的人,再令其出列。如此下去,直到圈中所有人出列为止。求出列编号序列。 基本要求   需要基于线性表的基本操作来实现约瑟夫问题   需要利用数组来实现线性表      输入输出格式   输入格式:n,m   输出格式1:在字符界面上输出这n个数的输出序列    输出格式2:将这n个数的输出序列写入到文件中 选做内容 使用单链表来实现之 使用循环链表来实现之 测试用例   输入:10,3   输出:3 6 9 2 7 1 8 5 10 4

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值