约瑟夫环问题详解

约瑟夫环

故事背景:

据说著名犹太历史学家 Josephus 有过以下的故事:在罗马人占领乔塔帕特后,39个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人 开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止,然而 Josephus 和他的朋友并不想遵从,Josephus 要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

问题描述:

有n个人围成一个环,然后给从某个人开始顺时针从1开始报数,每报到m时,将此人出环杀死,然后从下一个人继续从1报数,直到最后只剩下一个人,求这个唯一剩下的存活的人是谁?并求出环的顺序。
这里写图片描述

内圈是排列顺序,外圈是出环的顺序,也就是自杀的顺序。
41个人而报数3的约琴夫排列如下所示:
14 36 1 38 15 2 24 30 3 16 34 4 25 17 5 40 31 6 18 26 7 37 19 8 35 27 9 20 32 10 41 21 11 28 39 12 22 33 13 29 23
追后两个剩下的便是 Josephus 和他的朋友。。

循环链表模拟:

有序循环链表(无头尾节点):

#include<stdio.h>
#include<stdlib.h>
typedef struct node{
    int data;
    struct node *next;
}Lnode,*Linklist;

void Printlist(Linklist L ){
    Linklist p;
    p=L;
    if(L->next!=NULL){
    printf("%d ",p->data);
    p=p->next;
    while(p!=L && p->next!=p){
        printf("%d ",p->data);
        p=p->next;
    }//while
    printf("\n");
}   //if
} //Printlist
void Createlist(Linklist *L, int n){
    Linklist head,r,s;
    int i;
    (*L)=(Linklist)malloc(sizeof(Lnode));
    (*L)->next=NULL;
    (*L)->data=1;
    head=(*L);
    r=(*L);                 //使用一个中间结点再循环中让指向后移,实现尾插 
    for(i=2; i<=n; i++){
        s=(Linklist)malloc(sizeof(Lnode));
        s->data=i;
        r->next=s;
        r=s;
    }
    r->next=head;
}

void Outlist(Linklist *L, int m, int n){
    Linklist pre,p;
    int j,i=1;
    p=(*L);
//  p=pre->next;
    while(i<m){ //找到从传来的m位开始的地方 
        pre=p;
        p=p->next;
        i++;
    }
    while(p->next != p){   //历编这个链表直到剩下最后一个
         for(j=1; j<n; j++){
            pre=p;
            p=p->next;
         }
         printf("%d ",p->data);
         pre->next=p->next;
         free(p);
         p=pre->next;

    }
    printf("%d ",p->data);
    (*L)->next=NULL;
}

int main()
{
    Linklist L;
    int x,m,n;
    printf("输入要输入的环的组成数: ");
    scanf("%d", &x);
    Createlist(&L, x);
    Printlist(L);
    printf("输入要从第几个数数到第几个数: "); 
    scanf("%d%d",&m,&n);
    Outlist(&L, m, n);
    printf("\n最后输出表中剩下的数: ");
    Printlist(L);
    printf("\n");
    return 0;

}

代码分析:需要模拟游戏过程时间复杂度达到了O(m*n)

数组模拟
    //约瑟夫环,数组模拟
#include<stdio.h>
int main()
{
    int i, n,m,a[10001],dead=0,num=0;
    scanf("%d%d",&m,&n);
    for(i=1 ;i<=n; i++)
        a[i]=i;
    for(i=1; ; i++){
        if( i>n )   
            i=i%n; //数到大于人数的时候从头在开始
        if( a[i] > 0){ //活着就报数 
            num++;
        } 
        if(m==num && dead != n-1){//数到第 M 个数时,下个一重新开始计数,数组设置为0
            num=0;
            printf("%d ",a[i]);
            dead++;
            a[i]=0;
        } 
        else if(m==num && dead == n-1 ){
            printf("%d ",a[i]);
            break;
        }
    }
    return 0;
 } 

代码分析:需要模拟整个游戏流程时间复杂度为O(m*(n-1))

递归

如果不模拟游戏过程,把问题改变成1~n的人报数,报到m就要退出,那么这时还剩下n-1个人,退出的人不理他再把这n-1个人看成一个新的环,(开始从m mod n)再进行报数到m个人退出。
环变成了 k k+1 k+2 … 1 2 3 ….k-3 k-2

新的环重新编号
k1
k+12
k+23
..
k-3n-2
k-2n-1

把规模为n的问题变成规模为n-1的子问题 。类推直到1,使用递归
递归边界是n=1时 f[1]=0;
递归方程为 f[i]=(f[i-1]+m) mod i; (i>1)

//约瑟夫环, 递归


#include<stdio.h>

int f(int n, int m){
    if(n==1)    return 0;
    else return (f(n-1,m)+m)%n;
} 


int main()
{
    int m, n;
    scanf("%d %d",&n,&m);
    printf("%d\n", f(n,m)+1);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值