数据结构实战——Joseph 环的实现

转载请声明出处:http://blog.csdn.net/zhongkelee/article/details/40594147

问题描述

    约瑟夫环问题(Joseph)又称丢手绢问题:已知 m 个人围坐成一圈,由某人起头,下一个人开始从 1 递增报数,报到数字 n 的那个人出列,他的下一个人又从 1 开始报数,数到 n 的那个人又出列;依此规律重复下去,直到 m 个人全部出列约瑟夫环结束。如果从 0 ~ (m-1) 给这 m 个人编号,请输出这 m 个人的出列顺序。

解决思路

    本题的解决思路有两种:一是用循环链表解决,这会在后续的博客中给出解决方法;二是用线性表数组表示法解决。

    线性表数组解决方式有 2 种:1 是利用上篇博客提到的 ListDelete 函数,凡是数到 n 的那个出列元素直接删去;2 是设置标记,凡是数到 n 的那个出列元素,置为 -1 ,下一轮开始值为 -1 的不参与报数。

    如何用数组表示循环呢,即现在的问题是,数到 m-1 ,如何再寻回至0 ?这里可以采用 取模运算符 % ,即  i = (i+1) % m ,其中 i = [ 0 , m-1] 。


详细代码

/* 实现Joseph Ring */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "SlistOperation.h"

void InitJoseph(SLIST &L, int num){
    for(int i=1;i<=num;i++){
        ListInsert(L,i,i-1);
    }
}

void Joseph_1(SLIST L, int jump){
    int st = 0, pos = 1, item, temp;
    while(!ListEmpty(L)){
        while( st != jump ){
            GetElem(L,pos,temp);    //获取当前pos位序的元素temp
            if (pos < ListLength(L)) //获取temp的后继item
                NextElem(L,temp,item);
            else
                item = L.A[0];
            pos = LocateElem(L,item);   //获取后继item的位序
            st++;   //用于判断出圈元素
        }
        ListDelete(L,pos,temp); //满足条件的pos即删除并打印
        printf("%d ",temp);
        st = 1;
        if (pos > ListLength(L))
            pos = 1;
    }
    printf("\n");
}

void Joseph_2(SLIST L, int num, int jump){
    int i = 0, pos = 1;
    for (int j = 0; j < num; j++){	//规定输出次数为m
        while (i != jump){
            if (pos == ListLength(L))	//获取下一位
                pos = 1;
            else
                pos++;
            if (L.A[pos-1] >= 0)	//当前位置为-1,说明已出列
                i++;
        }
        i = 0;
        printf("%d ",L.A[pos-1]);
        L.A[pos-1] = -1;	//出列位记为-1
    }
}

int main(){
    int m,n;
    SLIST L;
    InitList(L);
    printf("请输入环的总数: ");
    scanf("%d",&m);
    printf("请输入跳数:");
    scanf("%d",&n);
    if (m<=0 || n<=0){
        printf("Input Error\n");
        system("pause");
        return 0;
    }

    InitJoseph(L,m);
    printf("\n变换前的初始环为:");
    ListTravers(L);
    printf("\n约瑟夫环解法一为:");  //ListDelete()删除出队元素
    Joseph_1(L,n);
    printf("\n约瑟夫环解法二为:");  //出队元素置为-1
    Joseph_2(L,m,n);
    printf("\n");
    return 0;
}

程序运行的结果示例,截图如下:


数学求解

    数学求解的思路是将约瑟夫问题归纳为数学问题,即推导出递推公式。好处是合理采用数学策略,使得编程简单,时间复杂度为 O(n),但是只能得到最后出列的那个人的序号。
    对于 m 个人,编号为 0~(m-1) ,其中第 k 个人出列,即 k-1 出列。
-> 0, 1,... ... ,k-1,k,k+1,... ...,m-1 (1)
    出列元素为 (n-1) % m。
-> 0, 1,... ... ,k-2,k,k+1,... ...,m-1
    延长此序列为:
-> 0, 1,... ... ,k-2,k,k+1,... ...,m-1,m,m+1,... ...,m+k-2
    出去 0~k-2 元素
-> k,k+1,... ...,m-1,m,m+1,... ...,m+k-2
    减去k
-> 0, 1,... ... ,m-2 (2)
    
    上述过程演示的就是 m 个人到 m-1 个人的变化,即可以通过递推得到约瑟夫环最后一个输出元素。现在从(2)回推 (1) ,假设(2)中只剩下最后一个元素 x,如果回推到(1)中对应的位序是x`,那么问题就得到了解决。
    显然我们可以得到 x` = (x+k) % m,于是就可以得到递推公式:f[1] = 0;f[i] = (f[i-1] + n) % i ,(i > 1)。

微笑 好了,这个问题就说到这儿。有问题请联系我:lichunchun4.0@gmail.com

转载请声明出处:http://bolg.csdn.net/zhongkelee


  • 9
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值