目录
一、实验作业相关要求
实验三 循环链表
1、实验目的与要求
1)熟悉循环链表的类型定义和基本操作;
2)灵活应用循环链表解决具体应用问题。
2、实验内容
有n个小孩围成一圈,给他们从1开始依次编号,从编号为1的小孩开始报数,数到第m个小孩出列,然后从出列的下一个小孩开始重新报数,数到第m个小孩又出列,……如此反复直到所有的小孩全部出列为止,求整个出列序列,例如:当n=6,m=5 时的出列序列是5,4,6,2,3,1 .
3、实验结果
1)请将调试通过的主要源代码、输出结果粘贴在下面
2)简述算法步骤,格式如下:
S1:
S2:
S3:
3)请分析算法的时间复杂度。
二、源代码
引用include部分
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;
定义结点类
//定义结点类
class LinkNode{
public:
int val;
LinkNode* next;
LinkNode() :val(0), next(NULL) {}
LinkNode(int x) : val(x), next(NULL) {}
LinkNode(int x, LinkNode *next) : val(x), next(next) {}
};
定义环形链表类
//定义环形链表类
class CircleList: public LinkNode{
public:
CircleList();
~CircleList();
bool makeEmpty();
int getSize();
void outAllelems();
bool Insert(int pos,int val);
bool Insert(int val);
bool isEmpty();
int outOneelem(int pos);
int getPos(int val);
bool remove(int pos);
LinkNode*& getHeadnode();
LinkNode*& getTailnode();
private:
LinkNode* head;
LinkNode* tail;
};
构造与析构
//初始化
CircleList::CircleList(){
head=new LinkNode();
tail=head;
tail->next=head;
}
//析构函数
CircleList::~CircleList(){
makeEmpty();
delete head;
}
置为空表
//置为空表
bool CircleList::makeEmpty(){
if(tail==head){
return true;
}else{
LinkNode* cur=head->next;
while(cur){
if(cur==tail){
head->next=head;
tail=head;
delete cur;
break;
}
cur=cur->next;
delete head->next;
head->next=cur;
}
return true;
}
}
获取链表大小
//获取链表大小
int CircleList::getSize(){
if(tail==head){
return 0;
}
LinkNode* cur=head->next;
int output=0;
while(cur){
output++;
if(cur==tail){
break;
}
cur=cur->next;
}
return output;
}
输出所有元素
//输出所有元素
void CircleList::outAllelems(){
if(head==tail){
return;
}
LinkNode* cur=head->next;
while(cur){
cout<<cur->val<<" ";
if(cur==tail){
break;
}
cur=cur->next;
}
cout<<endl;
}
在特定位置插入元素
//在特定位置插入元素
bool CircleList::Insert(int pos,int val){
if(pos<=0){
cerr<<"Position Error!!!"<<endl;
exit(-1);
}else{
if(this->isEmpty()){
if(pos==1){
this->Insert(val);
return true;
}else{
cerr<<"Position Error!!!"<<endl;
exit(-1);
}
}else{
LinkNode* cur=head;
for(int i=0;!bool(i&&cur==head);i++,cur=cur->next){
if(pos==i+1){
if(cur==tail){
LinkNode* newnode=new LinkNode(val);
newnode->next=head;
tail->next=newnode;
tail=newnode;
return true;
}else{
LinkNode* newnode=new LinkNode(val);
newnode->next=cur->next;
cur->next=newnode;
return true;
}
}
}
cerr<<"beyound range!!"<<endl;
return false;
}
}
}
后插一个元素 和 判空
//后插一个元素
bool CircleList::Insert(int val){
LinkNode* newnode=new LinkNode(val);
newnode->next=head;
tail->next=newnode;
tail=newnode;
return true;
}
//判断是否为空
bool CircleList::isEmpty(){
return head==tail?true:false;
}
获取特定位置的元素和获取特定元素的第一个位置
//获取特定位置的元素
int CircleList::outOneelem(int pos){
if(pos<=0||this->isEmpty()){
cerr<<"error"<<endl;
exit(-1);
}
LinkNode* cur=head->next;
for(int i=1;cur!=head;i++,cur=cur->next){
if(i==pos){
return cur->val;
}
}
cerr<<"position error"<<endl;
exit(-1);
}
//获取特定元素的第一个位置
int CircleList::getPos(int val){
if(this->isEmpty()){
cerr<<"error"<<endl;
exit(-1);
}
LinkNode* cur=head->next;
for(int i=1;cur!=head;i++,cur=cur->next){
if(val==cur->val){
return i;
}
}
cerr<<"position error"<<endl;
exit(-1);
}
删除特定位置的结点
//删除特定位置的结点
bool CircleList::remove(int pos){
if(pos<=0){
cerr<<"position error"<<endl;
exit(-1);
}else{
if(this->isEmpty()){
cerr<<"position error!!"<<endl;
exit(-1);
}else{
LinkNode* cur=head;
for(int i=0;cur!=tail;i++,cur=cur->next){
if(pos==i+1){
if(cur->next==tail){
LinkNode* del=tail;
cur->next=head;
tail=cur;
delete del;
return true;
}else{
LinkNode* del=cur->next;
cur->next=cur->next->next;
delete del;
return true;
}
}
}
cerr<<"beyound range!!!"<<endl;
return false;
}
}
}
获取头尾指针的引用
//获取头结点的引用
LinkNode*& CircleList::getHeadnode(){
return head;
}
//获取尾结点的引用
LinkNode*& CircleList::getTailnode(){
return tail;
}
定义方法类(仿照力扣)
//定义方法类(仿照力扣)
class Solution{
public:
//环形链表出列函数
vector<int> separationOrderfromCircleList(CircleList& cl, int num, int skipTimes){
vector<int> list;
if(cl.getHeadnode()==cl.getTailnode()){
return list;
}
if(skipTimes>=num){
for(int ptr=skipTimes%num;bool(num);num--,ptr=(ptr-1+skipTimes)%max(num,1)){ //
list.push_back(cl.outOneelem((ptr=(ptr==0?num:ptr))));
cl.remove(ptr);
}
return list;
}else{
LinkNode* cur=cl.getHeadnode();
for(int i=1;i<=num;i++){
for(int j=1;j<=skipTimes-1;j++,cur=(cur->next==cl.getTailnode()?cl.getHeadnode():cur->next));
list.push_back(cur->next->val);
LinkNode* del=cur->next;
cur->next=del->next;
if(cl.getTailnode()==del){
cl.getTailnode()=cur;
cur=cl.getHeadnode();
}
delete del;
}
return list;
}
}
};
主函数部分
int main(){
int n,m;
cin>>n>>m;
CircleList cl;
//插入链表中n个编号
for(int i=1;i<=n;i++){
cl.Insert(i);
}
vector<int> output=Solution().separationOrderfromCircleList(cl,n,m);
for(int i=0;i<output.size();i++){
cout<<output[i]<<" ";
}
cout<<endl;
return 0;
}
执行效果
当输入n=6,m=5时,
当输入n=8,m=3时,
当输入n=6,m=9时,
三、作业提交的文档内容
1)请将调试通过的主要源代码粘贴在下面
如上
2)简述算法步骤:
S1:定义链表复合类并且实现其基本操作如插入、删除等
S2:定义Solution工具类,在其中完成解约瑟夫难题的基本过程
S3:主函数创建一个环形链表,并调用工具类函数,输出答案
3)请分析算法的时间复杂度。
简而言之,对于一个环形单向链表
前插、后插时间复杂度均为O(1)
而插入、删除特定位置元素的时间复杂度为O(n)
其余需遍历链表操作的时间复杂度均为O(n)
对于解约瑟夫难题的部分
当 m(每m个人出列)<n(在n个人中) 时,
此时可采用双层遍历,第一层n次,第二层m次,意思就是对于每次查找一个人出列都循环m次,供需使n(或n-1)个人出列,这是典型的暴力解法,时间复杂度为 O(m*n)
而当 m>=n 时,
采用上述做法显然会重复遍历元素,开始绕圈,所以可以加一点,用m整除n来简化第二层循环,使第二层循环最多n次即可,时间复杂度为 O(n^2)
vector<int> separationOrderfromCircleList(CircleList& cl, int num, int skipTimes){
vector<int> list;
if(cl.getHeadnode()==cl.getTailnode()){
return list;
}
if(skipTimes>=num){
for(int ptr=skipTimes%num;bool(num);num--,ptr=(ptr-1+skipTimes)%max(num,1)){ //
list.push_back(cl.outOneelem((ptr=(ptr==0?num:ptr))));
cl.remove(ptr);
}
return list;
}else{
LinkNode* cur=cl.getHeadnode();
for(int i=1;i<=num;i++){
for(int j=1;j<=skipTimes-1;j++,cur=(cur->next==cl.getTailnode()?cl.getHeadnode():cur->next));
list.push_back(cur->next->val);
LinkNode* del=cur->next;
cur->next=del->next;
if(cl.getTailnode()==del){
cl.getTailnode()=cur;
cur=cl.getHeadnode();
}
delete del;
}
return list;
}
}
综上,解约瑟夫难题的时间复杂度为 min(O(n^2),O(m*n))
补充:看到有关于约瑟夫问题的 O(n) 时间复杂度的进阶算法
链表问题—环形单链表的约瑟夫问题_冰殇的博客-CSDN博客