【数据结构与算法(c语言)】线性表--循环链表 一张图搞懂 约瑟夫问题

一. 概念

循环链表 :

单链表中的最后一个结点的指针与指向头节点,整个链表形成一个环,这种链表称为循环链表

点击单链表复习

约瑟夫问题:

据说著名犹太历史学家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;
}

运行结果:
在这里插入图片描述

  • 28
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值