当前位置: 首页 > article >正文

约瑟夫问题

约瑟夫问题是一个经典的数学问题,也是计算机科学中常见的数据结构和算法题目之一。它的形式是:有n个人站成一排,从第一个人开始报数,每次报到m的人出列,直到所有人都出列为止。请问,最后留下的人原来在什么位置上?

这个问题可以用多种方法解决,其中包括使用数组、单链表、双向循环链表以及数学公式法等。下面我们将分别介绍这些解题方法的思路和实现。

1. 使用数组

如果我们使用数组来模拟约瑟夫问题,可以先创建一个长度为n的数组,表示n个人的编号。然后我们可以使用循环遍历该数组,并根据报数的规则删除数组中的元素,相当于把这个数组看作一个环形队列。

具体来说,从第一个人开始报数,每报到m的人就将其从数组中删除(将其值设为-1),并把后面的元素依次向前移动一位。然后从下一个人重新开始报数,直到所有人都出列。最后留下的那个人的编号就是数组中唯一一个没有被删除的数。

时间复杂度为O(nm),其中n表示人数,m表示报数到m的人出列。

#include <iostream>
#include <vector>
using namespace std;

vector<int> josephus(int n, int m) {
    vector<int> res;                    // 存储出列顺序
    vector<bool> used(n, false);        // 标记每个人是否已经出列
    int count = 0, index = -1;          // count表示当前报数的人数,index表示当前报数的人的下标
    while (res.size() < n) {            // 循环直到所有人都出列为止
        index++;                        // 按顺序遍历人数列表
        if (index >= n) index = 0;      // 处理超出范围的情况
        if (!used[index]) count++;      // 如果这个人还没有出列,则更新计数器
        if (count == m) {               // 如果这个人需要出列
            res.push_back(index + 1);   // 将他的编号存储在出列顺序列表中
            used[index] = true;         // 标记这个人已经出列
            count = 0;                  // 重置计数器
        }
    }
    return res;
}

int main() {
    int n, m;
    cin >> n >> m;
    auto res = josephus(n, m);
    for (auto x : res) {
        cout << x << " ";
    }
    cout << endl;
    return 0;
}

2. 使用单链表

如果我们使用单链表来模拟约瑟夫问题,我们可以创建一个带有n个节点的单链表,并将它闭合成环形链表。然后,从头结点开始依次遍历链表,每次找到要出列的节点的前一个节点,输出并删除该节点。因为单链表只能从前往后遍历,所以每次找到要出列的节点的前一个节点需要顺时针遍历m-1个节点。

时间复杂度为O(nm),与数组解法相同。

#include <iostream>
using namespace std;

struct Node {
    int data;
    Node* next;
    Node(int d): data(d), next(nullptr) {}
};

void josephus(int n, int m) {
    Node* head = new Node(1);
    Node* prev = head;
    for (int i = 2; i <= n; i++) {
        Node* curr = new Node(i);
        prev->next = curr;
        prev = curr;
    }
    prev->next = head;  // 将链表闭合成环
    
    Node* curr = head;
    while (n > 0) {
        // 找到要出列的节点的前一个节点
        for (int i = 0; i < m-1; i++) {
            curr = curr->next;
        }
        // 输出并删除节点
        cout << curr->next->data << " ";
        Node* temp = curr->next;
        curr->next = curr->next->next;
        delete temp;
        n--;
    }

    // 释放内存
    Node* temp = head;
    while (temp != nullptr) {
        Node* next = temp->next;
        delete temp;
        temp = next;
    }
}

int main() {
    josephus(5, 3);  // 输出: 3 1 5 2 4
    return 0;
}

3. 使用双向循环链表

如果我们使用双向循环链表来模拟约瑟夫问题,我们可以创建一个带有n个节点的双向循环链表,并将它闭合成环形链表。然后,从头结点开始依次遍历链表,每次找到要出列的节点的前一个节点,输出并删除该节点。因为双向循环链表可以从前往后遍历,也可以从后往前遍历,所以每次找到要出列的节点的前一个节点需要顺时针或逆时针遍历m-1个节点。

时间复杂度为O(nm),与单链表解法相同。

#include <iostream>
using namespace std;

struct Node {
    int data;
    Node* prev;
    Node* next;
    Node(int d): data(d), prev(nullptr), next(nullptr) {}
};

void josephus(int n, int m) {
    Node* head = new Node(1);
    Node* prev = head;
    for (int i = 2; i <= n; i++) {
        Node* curr = new Node(i);
        prev->next = curr;
        curr->prev = prev;
        prev = curr;
    }
    prev->next = head;  // 将链表闭合成环
    head->prev = prev;
    
    Node* curr = head;
    while (n > 0) {
        // 找到要出列的节点的前一个节点
        for (int i = 0; i < m-1; i++) {
            curr = curr->next;
        }
        // 输出并删除节点
        cout << curr->next->data << " ";
        Node* temp = curr->next;
        curr->next = temp->next;
        temp->next->prev = curr;
        delete temp;
        n--;
    }

    // 释放内存
    Node* temp = head;
    do {
        Node* next = temp->next;
        delete temp;
        temp = next;
    } while (temp != head);
}

int main() {
    josephus(5, 3);  // 输出: 3 1 5 2 4
    return 0;
}

4. 数学公式法

除了用数据结构来模拟约瑟夫问题,还有一种数学公式法可以解决这个问题。根据数学公式,最后留下来的那个人的编号是一个关于n和m的递推公式,我们可以直接用递推公式来计算出最后留下来的人的编号。

具体来说,最后留下来的那个人的编号可以表示成:

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

其中,f(n, m)表示n个人中最后留下来的人的编号,%表示取余操作。

我们可以使用递归或循环来计算递推公式。时间复杂度为O(n),与其他算法相比更加高效。

总结

约瑟夫问题是一个经典的数学问题,也是计算机科学中常见的数据结构和算法题目之一。

我们可以使用数组、单链表、双向循环链表以及数学公式法等多种方法


http://www.kler.cn/a/7369.html

相关文章:

  • GRE做题笔记(零散的个人经验)
  • Spark 之 Cache
  • Cartographer激光雷达slam -20241116
  • IDEA2024:右下角显示内存
  • SAFETY LAYERS IN ALIGNED LARGE LANGUAGEMODELS: THE KEY TO LLM SECURITY
  • IQ Offset之工厂实例分析
  • 【redis】redis分布式锁
  • 镜头光学指标介绍----清晰度SFR
  • 【从零开始学习 UVM】10.2、UVM TLM —— UVM TLM Blocking Put Port
  • 【CSAPP】进程 | 上下文切换 | 用户视角下的并发进程
  • 流量整形(GTS和LR)
  • 蓝桥杯之单片机学习(终)——关于之前文章的错误及更正(附:第十四届蓝桥杯单片机赛题)
  • L2-040 哲哲打游戏 简单模拟
  • 免费CRM如何进行选择?
  • 用GPT-4写代码不用翻墙了?Cursor告诉你:可以~~
  • 【视频分割】【深度学习】MiVOS官方Pytorch代码-S2M模块DeepLavV3Plus网络解析
  • 【Vue框架】Vue绑定样式及案例之行内样式——对象绑定样式与数组控制样式(附带源码案例)
  • 前端基础-ES6
  • 网络安全行业现在好混吗,工资水平怎么样?
  • Junit 5 单元测试框架
  • Matlab 一种计算植物面积密度的新方法(论文复现:凸包法)
  • 【C++】用一棵红黑树同时封装出map和set
  • 2022年业绩逆势增长,“要强”蒙牛再创蒙牛
  • Flutter 本地SQLite数据库版本升级处理
  • 数据分析之Pandas(2)
  • 【Go基础】一篇文章带你了解 — map