约瑟夫(Josephus)问题的求解——利用循环链表

约瑟夫(Josephus)问题的求解——利用循环链表

1. 约瑟夫问题的提法

  • 约瑟夫问题(约瑟夫环)是一个数学的应用问题。
  • 已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围,从编号为k的人开始报数,数到m的那个人出列,他的下一个人又从1开始报数,数到m的那个人又出列,依此规律重复下去,直到圆桌周围的人全部出列。
  • 通常解决这类问题时我们把编号从1~n,最后结果编号即为原问题的解。

2. 求解约瑟夫问题的算法原理

  • 注:n=8,m=3的约瑟夫问题示例,若n=8,k=1,m=3,则出列的顺序将为3,6,1,5,2,8,4,最初编号为7的就是约瑟夫问题的解。
    (1)一群人围在一起坐成环状,一共8个人,n=8。
    (2)从某个编号开始报数,假设从第一个人开始报数,k=1。
    (3)数到某个数的时候,此人出列,下一个人重新报数,设定出列的报数为3,m=3。
    (4)一直循环,直到所有人出列,约瑟夫环结束。
  • 求解约瑟夫问题的算法原理示例图:
    这里写图片描述

3. 利用循环链表求解约瑟夫问题

3.1 链表的结点结构定义
  • 文件:LinkNode.h

    
    #ifndef LINK_NODE_H_
    
    
    #define LINK_NODE_H_
    
    
    
    #include <iostream>
    
    
    #include <string>
    
    
    #include <strstream>
    
    
    using namespace std;
    
    template <class T>
    struct LinkNode         //链表结点类的定义
    {
        T data;             //数据域
        LinkNode<T> *link;  //指针域——后继指针
        //仅初始化指针成员的构造函数
        LinkNode(LinkNode<T>* ptr = NULL){ link = ptr; }
        //初始化数据与指针成员的构造函数
        LinkNode(const T& value, LinkNode<T>* ptr = NULL){ data = value; link = ptr; }
    };
    
    
    #endif /* LINK_NODE_H_ */
    
3.2 循环链表的类定义及其操作的实现(带附加头结点)
  • 文件:CircLinkedList.h

    
    #ifndef CIRC_LINKED_LIST_H_
    
    
    #define CIRC_LINKED_LIST_H_
    
    
    
    #include "LinkNode.h"
    
    
    template <class T>
    class CircLinkedList//带附加头结点
    {
    public:
        CircLinkedList();                               //构造函数
        ~CircLinkedList();                              //析构函数
    public:
        LinkNode<T>* Locate(int i)const;                    //获取第i个结点并返回
        bool Insert(int i, const T& x);                     //在第i个结点后插入数据值为x的新结点
        LinkNode<T>* Remove(LinkNode<T>* preNode, T& x);    //删除结点,并将被删结点的数据值保存至x,最后返回被删结点的下一个结点
        void MakeEmpty();                                   //清空链表  
    public:
        LinkNode<T>* GetHead()const;                //返回附加头结点的地址
    private:
        LinkNode<T> *first; //链表的头结点
    };
    
    //构造函数
    template<class T>
    CircLinkedList<T>::CircLinkedList()
    : first(new LinkNode<T>)
    {
        first->link = first;
        cout << "$ 执行构造函数" << endl;
    }
    
    //析构函数
    template<class T>
    CircLinkedList<T>::~CircLinkedList()
    {
        cout << "$ 执行析构函数" << endl;
        MakeEmpty();
    }
    
    //获取第i个结点并返回
    template<class T>
    LinkNode<T>* CircLinkedList<T>::Locate(int i)const
    {
        if (i < 0)
        {
            return NULL;
        }
        if ((first == first->link) || (0 == i))
        {
            return first;
        }
        int location = 1;
        LinkNode<T> *curNode = first->link;
        while ((location < i) && (first != curNode))
        {
            curNode = curNode->link;
            location++;
        }
        if (first == curNode)
        {
            curNode = NULL;
        }
        return curNode;
    }
    
    //在第i个结点后插入数据值为x的新结点
    template<class T>
    bool CircLinkedList<T>::Insert(int i, const T& x)
    {
        LinkNode<T> *curNode = Locate(i);
        if (NULL == curNode)
        {
            return false;
        }
        LinkNode<T> *newNode = new LinkNode<T>(x);
        if (NULL == newNode)
        {
            cerr << "* 存储分配错误" << endl;
            exit(1);
        }
        newNode->link = curNode->link;
        curNode->link = newNode;
        return true;
    }
    
    //删除结点,并将被删结点的数据值保存至x,最后返回被删结点的下一个结点
    template<class T>
    LinkNode<T>* CircLinkedList<T>::Remove(LinkNode<T>* preNode, T& x)
    {
        if (NULL == preNode)
        {
            return NULL;
        }
        LinkNode<T> *delNode = preNode->link;
        if (first == delNode)
        {
            delNode = first->link;
            first->link = delNode->link;
        }
        else
        {
            preNode->link = delNode->link;
        }
        x = delNode->data;
        LinkNode<T> *nextNode = delNode->link;
        delete delNode;
        return nextNode;
    }
    
    //清空链表(保留附加头结点)
    template<class T>
    void CircLinkedList<T>::MakeEmpty()
    {
        LinkNode<T> *curNode = NULL;
        while (first != first->link)        //当链表不为空时,删去链表中所有结点
        {
            curNode = first->link;          //保存被删结点
            first->link = curNode->link;    //将链表附加头结点中指针域的指针指向被删结点的下一个结点
            delete curNode;                 //从链表上摘下被删结点
        }
    }
    
    //返回附加头结点的地址
    template<class T>
    LinkNode<T>* CircLinkedList<T>::GetHead()const
    {
        return first;
    }
    
    
    #endif /* CIRC_LINKED_LIST_H_ */
    
3.3 求解约瑟夫环的算法实现
  • 文件:Josephus.h

        #ifndef JOSEPHUS_H_
        #define JOSEPHUS_H_
    
        #include "CircLinkedList.h" //带附加头结点
    
        template <class T>
        T Josephus(CircLinkedList<T>* Js, const int n, const int k, const int m)
        {
            T data;
            LinkNode<T> *preNode = NULL;
            LinkNode<T> *curNode = Js->Locate(k);
            for (int i = 0; i < n - 1; i++)
            {
                for (int j = 1; j < m; j++)
                {
                    if (Js->GetHead() == curNode)
                    {
                        curNode = curNode->link;
                    }
                    preNode = curNode;
                    curNode = curNode->link;
                }   
                curNode = Js->Remove(preNode, data);
                cout << "out-" << data << endl;
            }
            LinkNode<T> *solutionNode = Js->GetHead()->link;
            return solutionNode->data;
        }
    
        #endif /* JOSEPHUS_H_ */
        ```
    
    #### **3.4 主函数(main函数)的实现**
    
    - **文件:main.cpp**
        ```ruby:
    
    #include "Josephus.h"   //带附加头结点
    
    
    //判断输入的字符串每个字符是否都是数值1~9
    bool IsNumber(const string& s_num)
    {
        for (size_t i = 0; i < s_num.size(); i++)
        {
            if ((s_num[i] < '1') || (s_num[i] > '9'))
            {
                return false;
            }
        }
        return true;
    }
    
    //输入参数
    int get_parameter(const string& parameter_name)
    {
        cout << parameter_name;
        string s_item;
        cin >> s_item;
        while (false == IsNumber(s_item))
        {
            cout << "* 输入有误,请重新输入:";
            cin >> s_item;
        }
        return atoi(s_item.c_str());
    }
    
    //构造约瑟夫环
    template <class T>
    CircLinkedList<T>* construct_josephus()
    {
        cout << "==> 创建约瑟夫环" << endl;
        CircLinkedList<T> *cirlinkedList = new CircLinkedList<T>;
        return cirlinkedList;
    }
    
    //析构约瑟夫环
    template <class T>
    void destory_josephus(CircLinkedList<T>* cirlinkedList)
    {
        cout << "==> 释放约瑟夫环在堆中申请的空间,并将指向该空间的指针变量置为空" << endl;
        delete cirlinkedList;
        cirlinkedList = NULL;
    }
    
    int main(int argc, char* argv[])
    {
        int n = get_parameter("> 请输入总人数,n = ");
        int k = get_parameter("> 请输入开始报数的编号,k = ");
        int m = get_parameter("> 请输入报数间隔,m = ");
        CircLinkedList<int> *cirlinkedList = construct_josephus<int>();
        for (int i = 0; i < n; i++)
        {
            cirlinkedList->Insert(i, i+1);
        }
        int left = Josephus<int>(cirlinkedList, n, k, m);
        cout << "约瑟夫环的解,solution = " << left << endl;
        destory_josephus(cirlinkedList);
        system("pause");
        return 0;
    }

参考文献:
[1]《数据结构(用面向对象方法与C++语言描述)(第2版)》殷人昆——第二章
[2]《C/C++常用算法手册》秦姣华、向旭宇——第八章
[3] 百度搜索关键字:约瑟夫问题、约瑟夫环

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
循环链表是解决约瑟夫问题的一种常见方法。下面是一个使用Java循环链表解决约瑟夫问题的示例: ```java class Node { int data; Node next; public Node(int data) { this.data = data; } } class CircularLinkedList { private Node head; private Node tail; public void add(int data) { Node newNode = new Node(data); if (head == null) { head = newNode; tail = newNode; tail.next = head; } else { tail.next = newNode; tail = newNode; tail.next = head; } } public void remove(Node node) { if (head == null) { return; } if (head == node) { head = head.next; tail.next = head; } else { Node current = head; while (current.next != node) { current = current.next; } current.next = node.next; if (node == tail) { tail = current; tail.next = head; } } } public void josephus(int k) { Node current = head; while (current.next != current) { for (int i = 1; i < k; i++) { current = current.next; } System.out.print(current.data + " "); remove(current); current = current.next; } System.out.println(current.data); } } public class Main { public static void main(String[] args) { CircularLinkedList list = new CircularLinkedList(); int n = 7; // 总人数 int k = 3; // 报数为3的人出列 for (int i = 1; i <= n; i++) { list.add(i); } System.out.println("出列顺序:"); list.josephus(k); } } ``` 这段代码中,我们首先定义了一个`Node`类来表示循环链表的节点,然后定义了`CircularLinkedList`类来实现循环链表的操作。在`josephus`方法中,我们使用循环链表模拟约瑟夫问题的解决过程,每次找到第k个节点并移除,直到只剩下一个节点为止。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值