问题:把1-41个数字按从小到大的顺序首尾相接围成一个圆,然后从编号为1的数开始计数,每次数到第三个数就将其从圆中取出,直到最后剩余不足三个数字,运用C++语言中单循环链表的知识写出每次取出各元素的过程
问题分析
根据条件,首先要创建一个能容纳41个元素的单循环链表,因为要带头结点所以需要创建头指针,并将其指向自己,即成为循环链表;然后创建一个方法,用于循环遍历1~41赋值给链表中的数据元素,并将各元素一个一个链接起来,并把41指向首元结点;然后创建一个函数用来循环遍历输出链表中的所有元素,注意要添加分隔符来将各个元素隔开,还要注意最后一个元素的处理,即不需要输出分隔符;还要创建一个函数,用于找出链表每次数到第三个的元素,再将它删除,并返回当前数到的位序值;再创建一个函数用来返回表长,用在递归时判定是否还需要进行循环。最后在主程序中创建一个新的链表,包含41个元素,首先打印出当前内容来确定是否创建正确,然后先进行第一次调用,注意要传参“1”,表示从第一个数开始计数,然后通过限制表长大于3的情况下,一直让它递归调用,直到退出循环输出最后的结果,应该为16和31。
程序代码
// 约瑟夫环.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <ctime>
typedef int ElemType;
using namespace std;
struct Node {
ElemType data;
Node *next;
};
class CircularLinkList {
private:
Node *Head;//带头结点
public:
CircularLinkList();
void CreateList(int);
int CircularLinkListPrint();
int MinusList(int);
Node* DeleteElem(int);
int GetLength();
};
CircularLinkList::CircularLinkList() {
Head = new Node;
Head->next = Head;//分配存储空间并建立一个空表
}
void CircularLinkList::CreateList(int n) {
Node *p, *s;
p = Head;
p->next = NULL;//创建一个空链表
for (int i = 1; i <= n; i++) {
s = new Node;
s->data = i;
p->next = s;
p = s;
}
p->next = Head;//首尾相接形成循环
}
int CircularLinkList::CircularLinkListPrint() {//打印循环链表
Node *p;
p = Head->next;
while (p != Head) {
cout << p->data;
p = p->next;
if (p != Head)
cout << "->";
}
return 0;
}
int CircularLinkList::MinusList(int a) {
int j = a;
Node *p,*q;
p = Head->next;
q = p->next;
cout << endl << "取出:";
while (p != Head) {
if (j == 3) {//每数到第三个就输出
cout << p->data;
if (p != Head)
cout << "->";//不是头结点就输出->
p = DeleteElem((p->data));//根据值寻找到对应的元素删除,再将p重新赋值
j = 1;//重新计数
}
else {
p = p->next;
j++;
}
}
cout << "--下一个数是第" << j << "个数字";
return j;//返回数到数字的个数以便递归调用
}
Node* CircularLinkList::DeleteElem(int n) {
Node *p, *q;
p = Head->next;//从头结点开始寻找
while (p->next->data != n) {
p = p->next;
}//找到欲删除结点的前一个结点
q = p->next;
p->next = p->next->next;//让欲删除结点的前一个结点指向欲删除结点的后一个结点
q->next = NULL;
delete(q);//释放被删除结点的空间
return p->next;//返回下一个指针
}
int CircularLinkList::GetLength() {//获取循环链表的长度
int length = 0;
Node *p;
p = Head->next;
while (p != Head) {
length++;
p = p->next;
}
return length;
}
int main() {
int n = 41;
int t = 0;
cout << "(单循环链表)约瑟夫问题:1-41个数字首尾相接,每次数到第三个就取出,直到最后剩余不足三个" << endl;
CircularLinkList l;
l.CreateList(n);//首先创建1-41个数字的单循环链表
l.CircularLinkListPrint();
cout << endl << "开始处理:";
//l.MinusList(l.MinusList(l.MinusList(l.MinusList(l.MinusList(l.MinusList(l.MinusList(1)))))));
//直接递归调用,需要知道递归的次数
t = l.MinusList(1);//第一次调用需要给初值,即从第一个数开始数,将数完一圈后的是第几个数存储在t里
cout << endl << "剩余:";
l.CircularLinkListPrint();//打印出取出数出来的元素后的单循环链表
while (l.GetLength() >= 3) { //当单循环链表的长度大于3即还有多余三个数
t = l.MinusList(t);//递归调用
cout << endl << "剩余:";
l.CircularLinkListPrint();
}
cout << endl << "结果为:";
l.CircularLinkListPrint();//输出最后答案
return 0;
}
运行结果
(单循环链表)约瑟夫问题:1-41个数字首尾相接,每次数到第三个就取出,直到最后剩余不足三个
1->2->3->4->5->6->7->8->9->10->11->12->13->14->15->16->17->18->19->20->21->22->23->24->25->26->27->28->29->30->31->32->33->34->35->36->37->38->39->40->41
开始处理:
取出:3->6->9->12->15->18->21->24->27->30->33->36->39->–下一个数是第3个数字
剩余:1->2->4->5->7->8->10->11->13->14->16->17->19->20->22->23->25->26->28->29->31->32->34->35->37->38->40->41
取出:1->5->10->14->19->23->28->32->37->41->–下一个数是第1个数字
剩余:2->4->7->8->11->13->16->17->20->22->25->26->29->31->34->35->38->40
取出:7->13->20->26->34->40->–下一个数是第1个数字
剩余:2->4->8->11->16->17->22->25->29->31->35->38
取出:8->17->29->38->–下一个数是第1个数字
剩余:2->4->11->16->22->25->31->35
取出:11->25->–下一个数是第3个数字
剩余:2->4->16->22->31->35
取出:2->22->–下一个数是第3个数字
剩余:4->16->31->35
取出:4->35->–下一个数是第1个数字
剩余:16->31
结果为:16->31
注意事项
- 一定要保存每次调用后的位序值,如果没有保存而直接调用的话会导致每次循环都从当前列表的第一个开始计数
- 要设定递归的结束条件,否则会将整个单循环链表中的值都循环完
(2018-9-12已修复,通过DeleteElem返回p指针赋值给MinusList中的p指针)
最后还有点小问题:在释放被删除元素的空间“delete(q);”时,会出现在遍历链表中的元素出现任意值(如:87542941)的情况,但是如果不释放空间却不会出现这种情况,如果有解决的方案,欢迎在下面留言,谢谢!