约瑟夫(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] 百度搜索关键字:约瑟夫问题、约瑟夫环