目录
一. 概念
循环链表 :
单链表中的最后一个结点的指针与指向头节点,整个链表形成一个环,这种链表称为循环链表
点击单链表复习
约瑟夫问题:
据说著名犹太历史学家Josephus(弗拉维奥·约瑟夫斯)有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决。Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏
约瑟夫和他的朋友如何得到安全的位置,可以通过循环链表实现。
二. 图解
三. 代码
3.1 创建链表 CreateListTail
原型 :Status CreateListTail(LinkList * list ,int n)
说明 :a. 使用尾插法新建循环链表,
b. 第二个参数n,为链表的结点个数
c. 链表不需要不含任何元素的头结点。第一个结点就存放数据
d. 链表最后一个结点的next指针指向第一个结点,形成循环
代码 :
/* 新建一个不带头结点的循环链表,循环链表包含n个结点,结点的数据值为1到n */
Status CreateListTail(LinkList * list ,int n)
{
LNode * newNode, *pNode;
int i;
*list=(LNode*)malloc(sizeof(LNode)); //为第一个结点申请空间
if(*list == NULL)
return ERROR;
(*list)->data= 1; // 第一个结点的数据赋值1
pNode = *list;
for(i=2 ;i<=n;i++)
{
newNode=(LNode*)malloc(sizeof(LNode));
if(newNode == NULL){
DestoryList(list);
return ERROR;
}
else
{
newNode->data=i;
pNode= pNode->next=newNode; //先将newNode赋值给pNode->next, 然后将pNode指向地址变成pNode->next指向的地址
}
}
pNode->next = (*list); //最后一个几点的next指向第一个结点,形成循环
return OK;
}
3.2 销毁链表 DestoryList
原型 :void DestoryList(LinkList * list);
说明: 将链表list销毁,回收list中所有结点的内存
/*销毁线性表list,包括头结点的地址也释放*/
void DestoryList(LinkList * list)
{
LNode *nodePtr, *tempNodePtr;
nodePtr=(*list)->next; //从第2个结点开始释放
while(nodePtr!=(*list))
{
tempNodePtr=nodePtr; //使用临时指针存放需要释放的地址
nodePtr=nodePtr->next; //将游标指针指向下一个结点
free(tempNodePtr);
}
free(*list); //最后释放第一个结点
*list= NULL; //将链表地址设置为NULL,防止野指针
}
3.3 遍历链表 ListTraverse
原型 :Status ListTraverse(LinkList list, void (*Visit) (ElemType elem) );
说明: 遍历线性表,并对每一个元素调用Visit函数,本列中就是对list中的每个元素
/*遍历线性表,并对每一个元素调用Visit函数,一旦Visit()失败,这遍历失败 */
Status ListTraverse(LinkList list, void(*Visit) (ElemType elem) ){
LNode * nodePtr=list;
while(nodePtr->next!=list)//nodePtr->next!=list说明没有到最后一个元素
{
(*Visit)(nodePtr->data);
nodePtr=nodePtr->next;
}
(*Visit)(nodePtr->data);// 打印最后一个字符;
printf("\n");
return TRUE;
}
3.4 访问结点数据 Visit
原型 :void Visit(ElemType elem)
说明: 在此例中为打印elem数据,主要是作为ListTraverse的参数
void Visit(ElemType elem){
printf("%d\t",elem);
}
3.5 判断约瑟夫环的循环是否需要结束 CirculateIsEnd
原型 :Status CirculateIsEnd(LNode * currentNode,int m)
说明 :判断 约瑟夫环是否还剩m-1个结点(如果还剩m-1个,代表约瑟夫问题循环结束),如果是返回true,否则返回false
//此函数主要是判断 约瑟夫环是否还剩m-1个结点,如果是返回true,否则返回false
Status CirculateIsEnd(LNode * currentNode,int m)
{
int i=1;
LNode * startNode=currentNode;
while(i++<m)
{
currentNode=currentNode->next;
}
if(currentNode == startNode)
return TRUE; // 返回true,说明结点个数已经少于单次
else
return FALSE; // 返回失败!
}
3.6 约瑟夫环计算
原型 :void JosephusLink(LinkList * list,int m)
说明 :约瑟夫环计算,经过多轮删除结点,最后剩下m-1个结点
/* 约瑟夫环计算,经过多轮删除结点,最后剩下m-1个结点*/
void JosephusLink(LinkList * list,int m)
{
int i;
LNode * currentNode, * prevNode;// currentNode 循环到的当前结点,preNode当前结点的前一个结点
LNode * killedNode;
currentNode= *list;
while( !CirculateIsEnd(currentNode,m) ){ //当结点还剩下m-1个结点就停止循环
// 如果是求最后一个结点位置就不需要函数CirculateIsEnd,只需要判断currentNode->next !=list
for(i=1; i<= m-1; i++){
prevNode=currentNode;
currentNode=currentNode->next;
}
killedNode = currentNode; // 获取到要被自杀(删除的结点)
currentNode=currentNode->next;
prevNode->next = currentNode;
printf("%d号出局\n",killedNode->data);
free(killedNode);
}
*list=currentNode;//链表的地址(第一个结点的地址)有可能被删除了,将当前的结点地址赋值给list
}
3.6 全部代码
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status; /* 状态码,函数返回的状态码 */
typedef int ElemType; /* 元素类型,根据需要可以任意修改ElemType的原始类型,此处使用int型 */
typedef struct LNode
{
ElemType data ; /*节点数据, */
struct LNode * next; /*指向下一个节点的指针 */
} LNode, *LinkList; /*定义节点类型,并定义一个指向节点的指针 LinkList,作为线性表的开始*/
void Visit(ElemType elem);
void DestoryList(LinkList * list);
Status ListTraverse(LinkList list, void (*Visit) (ElemType elem) );
Status CreateListTail(LinkList * list ,int n);
Status CirculateIsEnd(LNode * currentNode,int m);
void JosephusLink(LinkList * list,int m);
/*销毁线性表list,包括头结点的地址也释放*/
void DestoryList(LinkList * list)
{
LNode *nodePtr, *tempNodePtr;
nodePtr=(*list)->next; //从第2个结点开始释放
while(nodePtr!=(*list))
{
tempNodePtr=nodePtr; //使用临时指针存放需要释放的地址
nodePtr=nodePtr->next; //将游标指针指向下一个结点
free(tempNodePtr);
}
free(*list);//最后释放第一个结点
*list= NULL;
}
/*对elem操作,具体什么操作可以根据需要修改,本示例中是打印elem的值*/
void Visit(ElemType elem){
printf("%d\t",elem);
}
/*遍历线性表,并对每一个元素调用Visit函数,一旦Visit()失败,这遍历失败 */
Status ListTraverse(LinkList list, void(*Visit) (ElemType elem) ){
LNode * nodePtr=list;
while(nodePtr->next!=list)//nodePtr->next!=list说明没有到最后一个元素
{
(*Visit)(nodePtr->data);
nodePtr=nodePtr->next;
}
(*Visit)(nodePtr->data);// 打印最后一个字符;
printf("\n");
return TRUE;
}
/* 新建一个不带头结点的循环链表,循环链表包含n个结点,结点的数据值为1到n */
Status CreateListTail(LinkList * list ,int n)
{
LNode * newNode, *pNode;
int i;
*list=(LNode*)malloc(sizeof(LNode)); //为第一个结点申请空间
if(*list == NULL)
return ERROR;
(*list)->data= 1; // 第一个结点的数据赋值1
pNode = *list;
for(i=2 ;i<=n;i++)
{
newNode=(LNode*)malloc(sizeof(LNode));
if(newNode == NULL){
DestoryList(list);
return ERROR;
}
else
{
newNode->data=i;
pNode= pNode->next=newNode; //先将newNode赋值给pNode->next, 然后将pNode指向地址变成pNode->next指向的地址
}
}
pNode->next = (*list); //最后一个几点的next指向第一个结点,形成循环
return OK;
}
//此函数主要是判断 约瑟夫环是否还剩m-1个结点,如果是返回true,否则返回false
Status CirculateIsEnd(LNode * currentNode,int m)
{
int i=1;
LNode * startNode=currentNode;
while(i++<m)
{
currentNode=currentNode->next;
}
if(currentNode == startNode)
return TRUE; // 返回true,说明结点个数已经少于单次
else
return FALSE; // 返回失败!
}
/* 约瑟夫环计算,经过多轮删除结点,最后剩下m-1个结点*/
void JosephusLink(LinkList * list,int m)
{
int i;
LNode * currentNode, * prevNode;// currentNode 循环到的当前结点,preNode当前结点的前一个结点
LNode * killedNode;
currentNode= *list;
while( !CirculateIsEnd(currentNode,m) ){ //当结点还剩下m-1个结点就停止循环/
// 如果是求最后一点就不需要函数CirculateIsEnd,只需要判断currentNode->next !=list
for(i=1; i<= m-1; i++){
prevNode=currentNode;
currentNode=currentNode->next;
}
killedNode = currentNode; // 获取到要被自杀(删除的结点)
currentNode=currentNode->next;
prevNode->next = currentNode;
printf("%d号出局\n",killedNode->data);
free(killedNode);
}
*list=currentNode;//链表的地址(第一个结点的地址)有可能被删除了,将当前的结点地址赋值给list
}
int main()
{
LinkList list;
CreateListTail( &list ,41);
printf("参加游戏的人员编号:\n\t");
ListTraverse(list,Visit);
printf("\n 游戏开始:\n\n");
JosephusLink(&list,3);
printf("\n约瑟夫和他的朋友应该站在:\t");
ListTraverse(list,Visit);
DestoryList(&list);
getchar();
return OK;
}
运行结果: