详解约瑟夫环问题及其相关的C语言算法实现

详解约瑟夫环问题及其相关的C语言算法实现

什么是约瑟夫环问题?

约瑟夫环问题是一个著名的数学问题,也称作是约瑟夫问题。一般来说,问题描述为:有 $n$ 个人围成一圈,从第 $k$ 个人开始报数,每报到第 $m$ 个人,就将该人从圈中杀死,然后从杀死该人的下一个人开始重新报数,直到圈中只剩下一个人为止。求圆圈中最后一个剩下的人的编号。

该问题有多种解法,其中比较常见的是使用链表模拟整个过程,或者是使用递归。

下面是 C 语言下的一种常见实现:

int josephus(int n, int k, int m) {
    int i, j;
    Node *t = head, *p = NULL;
    t->val = 1;

    for (i = 2; i <= n; ++i) {
        t = (t->next = (Node *) malloc(sizeof(Node)));
        t->val = i;
    }

    t->next = head;
    p = t;
    for (i = 0; i < k - 1; i++)
        p = p->next;

    while (t != t->next) {
        for (i = 0; i < m - 1; ++i) {
            p = t;
            t = t->next;
        }
        p->next = t->next;
        free(t);
        t = p->next;
    }
    return t->val;
}

算法实现说明

算法思路

我们可以用链表来模拟这个环,首先按顺序生成一个从1到n的链表,则环的最后一个节点的值就是最后的胜者。由于每次数到 m 时对应的节点要出链表,因此每一轮中数到 m 的节点找到并出链就转化为了当前节点向后走 m-1 步。

数据结构定义

我们需要定义一个简单的链表结构,在 C 语言中,可以使用 struct 来定义。

struct _Node {
    int val;
    struct _Node *next;
};

typedef struct _Node Node;

算法实现

  1. 定义链表头和尾,以及链表中每个节点的值 $val$。
  2. 循环生成节点,将其中 $val$ 的值依次赋值为 1~n,同时将结尾节点的 next 指向头部。
  3. 循环找到数到 $k$ 的节点,并标记该节点的 $val$ 为 $1$。
  4. 循环数到 $m$ 的节点,将当前节点 $t$ 在环中去掉,并将指向该节点的上一个节点 $p$ 的 next 指向 $t$ 的下一个节点,并释放掉 $t$ 节点的空间。
  5. 最终链表中只剩下一个节点,该节点的 $val$ 即为最后的胜者。

示例:

假设有 $n=5$ 个人围成一圈($1,2,3,4,5$):

  1. 规定从第 $k=3$ 个人开始报数
  2. 每次数到第 $m=2$ 个人就将当前该位置的人出圈,即被杀死
  3. 求出在约瑟夫环问题中最后存活下来的人的编号

我们可以使用以下 C 代码进行模拟实现:

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

struct _Node {
    int val;
    struct _Node *next;
};

typedef struct _Node Node;

int josephus(int n, int k, int m) {
    int i, j;
    Node *t = (Node *) malloc(sizeof(Node));
    Node *head = t, *p = NULL;
    t->val = 1;

    for (i = 2; i <= n; ++i) {
        t = (t->next = (Node *) malloc(sizeof(Node)));
        t->val = i;
    }

    t->next = head;
    p = t;
    for (i = 0; i < k - 1; i++)
        p = p->next;

    while (t != t->next) {
        for (i = 0; i < m - 1; ++i) {
            p = t;
            t = t->next;
        }
        p->next = t->next;
        free(t);
        t = p->next;
    }
    return t->val;
}

int main() {
    int n, k, m;
    printf("n = ");
    scanf("%d", &n);
    printf("k = ");
    scanf("%d", &k);
    printf("m = ");
    scanf("%d", &m);
    printf("被删掉的编号为:%d\n", josephus(n, k, m));
    return 0;
}

运行结果:

n = 5
k = 3
m = 2
被删掉的编号为:4

再假定有 $n=7$ 个人围成一圈($0,1,2,3,4,5,6$),从第 $k=2$ 个人开始报数,每次数到第 $m=3$ 个人就将当前该位置的人出圈,即被杀死,求出在约瑟夫环问题中最后存活下来的人的编号。可以在以下代码的基础上进行修改:

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

struct _Node {
    int val;
    struct _Node *next;
};

typedef struct _Node Node;

int josephus(int n, int k, int m) {
    int i, j;
    Node *t = (Node *) malloc(sizeof(Node));
    Node *head = t, *p = NULL;
    t->val = 0;

    for (i = 1; i <= n; ++i) {
        t = (t->next = (Node *) malloc(sizeof(Node)));
        t->val = i;
    }

    t->next = head;
    p = t;
    for (i = 0; i < k - 1; i++)
        p = p->next;

    while (t != t->next) {
        for (i = 0; i < m - 1; ++i) {
            p = t;
            t = t->next;
        }
        p->next = t->next;
        free(t);
        t = p->next;
    }
    return t->val;
}

int main() {
    int n, k, m;
    printf("n = ");
    scanf("%d", &n);
    printf("k = ");
    scanf("%d", &k);
    printf("m = ");
    scanf("%d", &m);
    printf("被删掉的编号为:%d\n", josephus(n, k, m));
    return 0;
}

运行结果:

n = 7
k = 2
m = 3
被删掉的编号为:6

这就是约瑟夫环问题的详解及相关C语言的算法实现。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解约瑟夫环问题及其相关的C语言算法实现 - Python技术站

(0)
上一篇 2023年5月22日
下一篇 2023年5月22日

相关文章

  • C++ STL标准库std::vector扩容时进行深复制原因详解

    C++ STL标准库std::vector是一个提供动态数组功能的容器,它提供了扩容机制,即当当前存储的元素个数达到容量限制时,会自动将容量扩大一倍,以适应更多元素的存储。但在扩容的过程中,每一个元素都必须进行深复制操作,这是因为在动态内存分配中,变量在内存中的位置不连续,因此需要将每个元素重新复制到新的内存位置上。 下面以两个简单示例详细说明std::ve…

    C 2023年5月23日
    00
  • C语言代码实现简单的扫雷小游戏

    C语言代码实现简单的扫雷小游戏 一、游戏规则 扫雷是一款经典的单人益智小游戏,游戏场景是一个区块是由许多个格子组成的矩形网格,有一部分格子下面隐藏着地雷,玩家通过揭露不带雷的部分,最终找到所有地雷的位置。 具体游戏规则: 鼠标左键点开或标记可疑格子。 若点击的是地雷,则游戏结束,显示所有地雷的位置。 若点击的是数字,则显示周边8个格子中地雷的数量。 若点击的…

    C 2023年5月23日
    00
  • Java虚拟机处理异常的最佳方式

    下面我将为您详细讲解Java虚拟机处理异常的最佳方式,这一攻略分为以下几个部分: 1. Java异常机制简介 在Java程序中,当发生异常时,会抛出一个异常对象,该对象包含了异常的类型、信息和发生异常的位置等信息,并将该异常对象传递给调用栈中的上层方法处理。Java中的异常分为受检查异常和非受检查异常两种。 受检查异常通常指那些在程序逻辑正确的情况下仍可能发…

    C 2023年5月22日
    00
  • Javascript OOP之面向对象

    JavaScript OOP之面向对象 在JavaScript中,面向对象编程是一种非常强大的技术。通过面向对象编程,我们可以将代码进行高效的封装和组织,便于后期的维护和扩展。 基本概念 在面向对象编程中,有三个基本概念:类、对象和方法。 类 类是一种抽象的数据类型,它描述了一类对象的属性和方法。比如,一个类可以是“人”,它包含了“姓名”、“年龄”、“性别”…

    C 2023年5月23日
    00
  • 升级Win8.1后传统start开始菜单不见了如何找回

    针对“升级Win8.1后传统start开始菜单不见了如何找回”的问题,我来给出完整的攻略: 问题描述 在升级Windows 8.1之后,原本存在的传统start开始菜单不见了,这该如何找回? 解决步骤 1. 检查任务栏设置 有时传统start开始菜单的隐藏可能是由于任务栏设置所导致的。可以按照以下步骤进行设置: 鼠标右键点击任务栏,并选择“属性”选项; 在弹…

    C 2023年5月24日
    00
  • 在C语言编程中设置和获取代码组数的方法

    设置和获取代码组数的方法主要是通过定义并使用数组的方式来实现的。下面是详细的C语言编程攻略: 创建一个数组来存储代码组数 首先,我们需要定义一个数组来存储代码组数。假设我们想要存储10组代码,可以这样定义一个名为code_num的整型数组: int code_num[10]; 在上面的代码中,我们定义了一个名为code_num的整型数组,并指定它的大小为10…

    C 2023年5月24日
    00
  • C语言可变参数列表的用法与深度剖析

    C语言可变参数列表的用法与深度剖析 C语言中的可变参数列表是一种强大的功能,它允许我们定义一个参数数量不定的函数。一般情况下,我们使用可变参数列表来编写那些需要处理不定数量参数的函数,例如printf函数和scanf函数。在本篇文章中,我们将对C语言可变参数列表的用法进行详细讲解,并给出两个示例说明。 什么是可变参数列表? 可变参数列表是指函数的参数数量是不…

    C 2023年5月23日
    00
  • c语言小游戏程序之弹跳小球的实现代码

    下面我来详细介绍“c语言小游戏程序之弹跳小球的实现代码”的完整攻略。 一、需求分析 首先需要明确这个小游戏的需求,即实现一个可以弹跳的小球,小球需要在屏幕内弹跳,并且小球碰撞到墙壁会反弹,小球下落时能够受到重力加速度的影响,小球的运动需要实时刷新。 二、实现思路 在明确了需求后,我们可以思考一下实现的思路: 定义小球的位置、速度、半径等参数,并设定重力加速度…

    C 2023年5月23日
    00
合作推广
合作推广
分享本页
返回顶部