前言
循环链表是一条首尾相连的单链表,对他的处理方式类似于单链表,其中有一个著名问题叫Joseph 问题
一、Joseph 问题
有 10 个小朋友按编号顺序 1,2,…,10 顺时针方向围成一圈。从 1 号开始顺时针方向 1,2,…,9 报数,凡报数 9 者出列(显然,第一个出圈为编号 9 者)。那么最后一个出圈者的编号是多少?第 5 个出圈者的编号是多少?
二、解题
1.创建循环链表
前面已经提到,循环链表是一条首尾相连的单链表,所以循环链表的创建与单链表类似,需要注意的一点是单链表的最后一个结点滞空,循环链表则是指向头结点。
代码如下:
typedef struct _LinkNode
{
int data;
struct _LinkNode *next;
}LinkList,LinkNode;
bool InitList(LinkList* &L)
{
L=new LinkNode;
if(!L)
{
return false;
}
else
{
L->next=L; //头节点指针域指向自己
L->data=-1;
return true;
}
}
2.尾插法插入数据
尾插法插入数据时,仍要注意我们新插入的结点的指针域要指向头结点,判断找到尾结点方式不再是尾结点指针域为空,而是尾结点指针域指向头结点。
代码如下:
bool InsertList_back(LinkList* &L,LinkNode* &node)
{
LinkNode *last=NULL;
last=L;
if(!L||!node)
{
return false;
}
else
{
while(last->next!=L) //last结点指针域未指向头结点时,说明last结点不是尾结点
{
last=last->next;
}
node->next=L; //新插入结点指针域指向头结点
last->next=node; //插入新结点
return true;
}
}
3.循环链表数据打印
这里我们需要注意的还是判断链表遍历完的条件是尾结点指向头结点。
代码如下:
void LinkPrint(LinkList *L)
{
LinkList *p;
p=L;
if(!L||L->next==L)
{
cout<<"链表为空!"<<endl;
}
else
{
p=L->next;
while(p!=L)
{
cout<<p->data<<"\t";
p=p->next;
}
}
cout<<endl;
}
4.Joseph 问题解答
代码如下:
bool Joseph(LinkList* &L,int interval) //interval表示出圈条件(出圈报的号数)
{
LinkList *p,*q;
int j=0,i=0;
int times=0,num=0; //times用于记录报号圈数,num记录出圈时该结点的编号
p=L;
if(!L||p->next==L)
{
cout<<"链表为空!"<<endl;
return false;
}
if(interval<1)
{
cout<<"报数淘汰口令不能小于 1!"<<endl;
return false;
}
do{
i+=interval;
while(p->next) //查找第i个节点,p指向该结点的上一个节点
{
if(p->next!=L)
j++;
if(j>=i)
break;
p=p->next;
}
times++;
q=p->next; //临时保存被删结点的地址以备释放空间
num=q->data;
if(times==5)
cout<<"第 5 个出圈的编号是:"<<num<<endl;
printf("出圈的结点: %d 上一个结点: %d 下一个结点:%d\n",q->data, p->data,q->next->data);
cout<<endl;
p->next=q->next; //改变删除结点前驱结点的指针域
delete q; //释放被删除结点的空间
LinkPrint(L);
}while(L->next!=L);
cout<<"最后一个出圈的编号是:"<<num<<endl;
return true;
}
5.源码
#include <iostream>
using namespace std;
typedef struct _LinkNode
{
int data;
struct _LinkNode *next;
}LinkList,LinkNode;
bool InitList(LinkList* &L)
{
L=new LinkNode;
if(!L)
{
return false;
}
else
{
L->next=L; //头节点指针域指向自己
L->data=-1;
return true;
}
}
bool InsertList_back(LinkList* &L,LinkNode* &node)
{
LinkNode *last=NULL;
last=L;
if(!L||!node)
{
return false;
}
else
{
while(last->next!=L) //last结点指针域未指向头结点时,说明last结点不是尾结点
{
last=last->next;
}
node->next=L; //新插入结点指针域指向头结点
last->next=node; //插入新结点
return true;
}
}
void LinkPrint(LinkList *L)
{
LinkList *p;
p=L;
if(!L||L->next==L)
{
cout<<"链表为空!"<<endl;
}
else
{
p=L->next;
while(p!=L)
{
cout<<p->data<<"\t";
p=p->next;
}
}
cout<<endl;
}
bool Joseph(LinkList* &L,int interval) //interval表示出圈条件(出圈报的号数)
{
LinkList *p,*q;
int j=0,i=0;
int times=0,num=0; //times用于记录报号圈数,num记录出圈时该结点的编号
p=L;
if(!L||p->next==L)
{
cout<<"链表为空!"<<endl;
return false;
}
if(interval<1)
{
cout<<"报数淘汰口令不能小于 1!"<<endl;
return false;
}
do{
i+=interval;
while(p->next) //查找第i个节点,p指向该结点的上一个节点
{
if(p->next!=L)
j++;
if(j>=i)
break;
p=p->next;
}
times++;
q=p->next; //临时保存被删结点的地址以备释放空间
num=q->data;
if(times==5)
cout<<"第 5 个出圈的编号是:"<<num<<endl;
printf("出圈的结点: %d 上一个结点: %d 下一个结点:%d\n",q->data, p->data,q->next->data);
cout<<endl;
p->next=q->next; //改变删除结点前驱结点的指针域
delete q; //释放被删除结点的空间
LinkPrint(L);
}while(L->next!=L);
cout<<"最后一个出圈的编号是:"<<num<<endl;
return true;
}
int main()
{
LinkList *L;
LinkNode *s;
int i=0;
if(InitList(L))
{
cout<<"初始化成功!"<<endl;
cout<<"尾插法创建循环链表,插入10个元素..."<<endl;
int i=0;
while((++i)<=10)
{
s=new LinkNode;
s->data=i;
s->next=NULL;
/*
if(InsertList_back(L,s))
{
cout<<"插入成功!"<<endl;
}
else
{
cout<<"插入失败!"<<endl;
}
*/
InsertList_back(L,s);
}
cout<<"插入的10个元素为:"<<endl;
LinkPrint(L);
cout<<endl;
LinkPrint(L);
Joseph(L, 9);
}
else
{
cout<<"初始化失败!"<<endl;
}
return 0;
}
6.运行结果
总结
循环链表是单链表的特殊情况,处理循环链表问题时需要注意循环链表尾结点是指向头结点的,牢牢卡住这个特性会让循环链表更容易理解。