目录
前言:
本文以类模板创建循环链表的方式解决约瑟夫问题,并非为最简方法,倾向于加强对类模板以及链表的学习及使用,全文仅供参考,如有不足的地方还望多多指教。
提示:以下是本篇文章正文内容,代码注释较为详细,可供参考
一、创建结点
创建类模板Node,结点是构成链表的基本单位,注意用要用成员函数访问其私有成员,不能直接访问。
class Node {
private:
T m_data;//数据域
Node* m_next = nullptr;//指针域
public:
Node(const T& val) {
this->m_data = val;
}//有参构造函数
const T& data()const {
return this->m_data;
}//const类型访问私有成员m_data
T& data() {
return this->m_data;
}//访问私有成员m_data
Node*& next() {
return this->m_next;
}//访问私有成员m_next
};
二、创建循环链表
1.类模板定义
template<class T>
class SList {
private:
Node<T>* m_head = nullptr, * m_tail = nullptr;//定义头指针和尾指针
int m_length = 0;//链表长度
public:
SList() = default;//使用默认构造函数
~SList() { clear(); };//析构函数
void clear();//清除链表
void push_back(const T& val);//尾插函数
void erase(const T& val);//删除结点
void game();//约瑟夫问题
void show();//打印链表(序号)
};
2.尾插函数
通过将尾指针指针域指向头指针来实现循环。
template<class T>
void SList<T>::push_back(const T& val) {
Node<T>* node = new Node<T>(val);//
if (this->m_head == nullptr) {
this->m_head = this->m_tail = node;
this->m_tail->next() = this->m_head;//尾指针指向头指针,实现循环
}//当链表为空的时候
else {
this->m_tail->next() = node;
this->m_tail = node;
this->m_tail->next() = this->m_head;
}//链表不为空的时候
this->m_length++;//插入后链表长度+1
}
3.删除结点
删除时要特别注意当删除对象是头结点和尾结点时头指针和尾指针的处理,一定要先将这两个指针指向改变再进行删除,否则在后面的操作中可能出现丢失链表以及野指针的问题。
template<class T>
void SList<T>::erase(const T& val) {
Node<T>* p = this->m_head, * q = p;
while (p != nullptr && p->data() != val) {
q = p;
p = p->next();
}//遍历链表
if (p) {
q->next() = p->next();
}
if (p == this->m_tail) {
this->m_tail = q;
}
if (p == this->m_head && p) {
this->m_head = p->next();
this->m_tail->next() = this->m_head;
}//当删除的结点为头节点时要先将头指针和尾指针指向下一个结点,否则可能出现野指针
delete p;
this->m_length--;//删除后链表长度-1
}
4.清除链表
清除时将尾指针指针域置空,这样便可以按照单向链表的清除方式进行逐个删除,删除完后记得将尾指针置空。
template<class T>
void SList<T>::clear() {
if (this->m_length == 0)
{
return;
}//若链表为空则返回
this->m_tail->next() = nullptr;//先将尾指针指针域置空,然后按照普通链表来进行逐个清除
Node<T>* p = nullptr;
while (this->m_head != nullptr) {
p = this->m_head;
this->m_head = this->m_head->next();
delete p;
}
this->m_tail = nullptr;//将尾指针置空,避免出现野指针
}
5.打印链表
用来输出剩余人数的序号(数据域)。
template<class T>
void SList<T>::show() {
Node<T>* p = this->m_head;
int first = this->m_head->data();
for (int i = 1; i <= this->m_length; i++) {
cout << p->data() << " ";
p = p->next();
}
}//遍历打印
三、约瑟夫问题
1.创建对应人数的循环链表
展示的是成员函数game的部分代码,通过for循环插入对应人数的结点,并将这些结点的数据域赋值对应的序号(1,2,3......n)。
template<class T>
void SList<T>::game() {
int pnum = 0, count = 0;
cout << "游戏人数为:" << endl << "报数为:" << endl;
cin >> pnum >> count;
for (int i = 1; i <= pnum; i++) {
push_back(i);
}//向循环链表插入相同人数的结点
cout << endl;
2.通过双指针和双循环实现
通过两个指针,其中countinueloc是用来记录被删除人的下一个结点地址,所以它从计数时就从第二个结点开始向前推进。而location指针则是每轮中用来确定该轮被删除人的位置,它总是从countinueloc指针的位置开始报数,随其一起向前推进,直到报数值然后被删除。在下一次轮次开始时又创建一个新的location指针重复上述操作,唯有countinueloc是每一次轮次都在持续推进循环。
第一个for循环是用来确定执行的轮次,直到剩下2个人,第二个for循环则是两个指针向前推进的位次,由报数值决定。
Node<T>* countinueloc = this->m_head;//设置一个指针用来记录被删除人的下一个位置,方便继续计数删除
for (int i = pnum; i > 2; i--) { //第一个for循环用来判断需要进行多少轮
Node<T>* location = countinueloc;//这个指针是用来执行每一轮删除任务的,每一轮斗从continueloc的位置开始进行计数
for (int i = 0; i < count; i++) {
location = countinueloc;
countinueloc = countinueloc->next();
}//第二个for用来报数,通过报数的值来决定指针前进多少个结点
erase(location->data());//删除location指针所指的结点
cout << "进行一轮后剩余的人序号:";
show();
cout << endl;
}
cout << "剩下获奖的2人序号是:";
show();
}
3.main函数
int main()
{
SList<int> game;
game.game();
}
四、完整代码
以下是完整代码展示,由于个人水平有限,如有错误/不严谨的地方敬请指正。
#include <iostream>
using namespace std;
template<class T>
class Node {
private:
T m_data;//数据域
Node* m_next = nullptr;//指针域
public:
Node(const T& val) {
this->m_data = val;
}//有参构造函数
const T& data()const {
return this->m_data;
}//const类型访问私有成员m_data
T& data() {
return this->m_data;
}//访问私有成员m_data
Node*& next() {
return this->m_next;
}//访问私有成员m_next
};
template<class T>
class SList {
private:
Node<T>* m_head = nullptr, * m_tail = nullptr;//定义头指针和尾指针
int m_length = 0;//链表长度
public:
SList() = default;//使用默认构造函数
~SList() { clear(); };//析构函数
void clear();//清除链表
void push_back(const T& val);//尾插函数
void erase(const T& val);//删除结点
void game();//约瑟夫问题
void show();//打印链表(序号)
};
template<class T>
void SList<T>::push_back(const T& val) {
Node<T>* node = new Node<T>(val);//
if (this->m_head == nullptr) {
this->m_head = this->m_tail = node;
this->m_tail->next() = this->m_head;//尾指针指向头指针,实现循环
}//当链表为空的时候
else {
this->m_tail->next() = node;
this->m_tail = node;
this->m_tail->next() = this->m_head;
}//链表不为空的时候
this->m_length++;//插入后链表长度+1
}
template<class T>
void SList<T>::erase(const T& val) {
Node<T>* p = this->m_head, * q = p;
while (p != nullptr && p->data() != val) {
q = p;
p = p->next();
}//遍历链表
if (p) {
q->next() = p->next();
}
if (p == this->m_tail) {
this->m_tail = q;
}
if (p == this->m_head && p) {
this->m_head = p->next();
this->m_tail->next() = this->m_head;
}//当删除的结点为头节点时要先将头指针和尾指针指向下一个结点,否则可能出现野指针
delete p;
this->m_length--;//删除后链表长度-1
}
template<class T>
void SList<T>::clear() {
if (this->m_length == 0)
{
return;
}//若链表为空则返回
this->m_tail->next() = nullptr;//先将尾指针指针域置空,然后按照普通链表来进行逐个清除
Node<T>* p = nullptr;
while (this->m_head != nullptr) {
p = this->m_head;
this->m_head = this->m_head->next();
delete p;
}
this->m_tail = nullptr;//将尾指针置空,避免出现野指针
}
template<class T>
void SList<T>::show() {
Node<T>* p = this->m_head;
int first = this->m_head->data();
for (int i = 1; i <= this->m_length; i++) {
cout << p->data() << " ";
p = p->next();
}
}//遍历打印
template<class T>
void SList<T>::game() {
int pnum = 0, count = 0;
cout << "游戏人数为:" << endl << "报数为:" << endl;
cin >> pnum >> count;
for (int i = 1; i <= pnum; i++) {
push_back(i);
}//向循环链表插入相同人数的结点
cout << endl;
Node<T>* countinueloc = this->m_head;//设置一个指针用来记录被删除人的下一个位置,方便继续计数删除
for (int i = pnum; i > 2; i--) { //第一个for循环用来判断需要进行多少轮
Node<T>* location = countinueloc;//这个指针是用来执行每一轮删除任务的,每一轮斗从continueloc的位置开始进行计数
for (int i = 0; i < count; i++) {
location = countinueloc;
countinueloc = countinueloc->next();
}//第二个for用来报数,通过报数的值来决定指针前进多少个结点
erase(location->data());//删除location指针所指的结点
cout << "进行一轮后剩余的人序号:";
show();
cout << endl;
}
cout << "剩下获奖的2人序号是:";
show();
}
int main()
{
SList<int> game;
game.game();
}