环形链表的约瑟夫问题

问题的描述

编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。

下一个人继续从 1 开始报数。

n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少?

形象化解释

想象一下,有n个人站成一个圈,就像玩传统的“热土豆”游戏。他们开始数数,每数到m就像是热土豆烫到了谁,那个人就得离开游戏。然后,游戏继续,从下一个人开始重新数数,直到只剩下一个人,这个人就是游戏的赢家。

现在的问题是:如果我们知道了总人数n和数到多少人要出局的数字m,我们怎样才能快速找出最后的赢家呢?

这里有一个聪明的方法,我们可以一步步回溯看最后的情况是如何影响前面的情况的。

  1. 最简单的情况:假设只有一个人,那他自然是赢家,因为他没有人可以数到了。
  2. 再复杂一点:如果有两个人,那看数数的数字m是多少,我们就能找出谁会留到最后。
  3. 更多的人:如果有3个、4个、5个...直到n个人,情况就变得复杂了。但是,无论圈里有多少人,每次有人出局时,圈都会缩小,我们可以反过来思考,从最后一轮逆推回第一轮,看看赢家是如何在每一轮中幸存下来的。

使用数学公式,我们可以把这个逆推的过程表达为一个简单的规则:对于每一轮,最后赢家的位置都是由前一轮赢家的位置加上m(数到多少人出局的数字),然后对当前圈中的人数取模(因为是圆圈,所以要循环)得到的。

这就像是我们有一个时间机器,可以回到每一轮看看谁会留下来,然后再跳回到最后,告诉你最初的赢家是谁。

具体示例

假设有5个人(编号为1到5),数到2的人出局。我们想知道最后留下的是谁。

1. 最开始,我们不知道谁会留到最后,但我们可以从最简单的情况开始:如果只有1个人,那么无论数到几,这个人(我们称之为位置0,因为我们从0开始计数)总是会留下来。

2. 现在假设有2个人,我们知道从1个人的情况,最后留下的是位置0。现在,如果我们数到2(m=2),那么会淘汰一个人,剩下的人是从上一轮的赢家(位置0)开始数的。在2个人的情况下,数2次实际上就是回到了起点,所以最后留下的还是位置0的人。

3. 增加到3个人,我们用同样的逻辑,上一轮留下的是位置0的人。现在我们再数2次,但因为人更多了,数2次会落在位置1的人上( (0 + 2) % 3 = 2 ),所以在3个人的情况下,最后留下的是位置2的人。

4. 增加到4个人,根据上一轮的结果,我们从位置2开始数,数2次( (2 + 2) % 4 = 0 ),意味着最后留下的是位置0的人。

5. 最后,增加到5个人,我们从位置0的人开始数,数2次( (0 + 2) % 5 = 2 ),这意味着在5个人的情况下,最后留下的是位置2的人。

所以,通过这个规则,我们发现在有5个人,数到2的情况下,最后留下的是编号为3的人(因为我们的计算是从0开始的,所以位置2对应的是编号3)。

这个过程可以通过一个递推公式来概括,对于n个人数到m的情况,最后留下人的位置可以表示为:

f ( n , m ) = ( f ( n−1, m ) + m ) % n

其中,f ( 1, m ) = 0 ,因为如果只有一个人,那么他就是最后的赢家。

这个递推公式就是我们一步一步计算的基础。通过它,我们可以从最简单的情况开始,逐步增加人数,直到达到实际的人数n,从而找到最后留下的人的位置。

代码实现

首先,我们来看看基于模拟的方法如何实现:

#include <stdio.h>

// 模拟法解决约瑟夫问题
int josephus(int n, int m) {
    int last = 0; // 最后留下的人的初始编号
    for (int i = 2; i <= n; i++) {
        last = (last + m) % i;
    }
    return last + 1; // 由于数组是从0开始的,而题目中编号是从1开始的,所以需要+1
}

int main() {
    int n, m;
    printf("请输入n和m的值:");
    scanf("%d%d", &n, &m);
    printf("最后留下的人的编号是:%d\n", josephus(n, m));
    return 0;
}

然后,是基于数学公式直接计算的方法:

#include <stdio.h>

// 数学法解决约瑟夫问题
int josephus(int n, int m) {
    if (n == 1)
        return 1; // 只有一个人时,直接返回1
    else
        // 根据公式递归计算
        return (josephus(n - 1, m) + m - 1) % n + 1;
}

int main() {
    int n, m;
    printf("请输入n和m的值:");
    scanf("%d%d", &n, &m);
    printf("最后留下的人的编号是:%d\n", josephus(n, m));
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值