C语言解决约瑟夫环问题

题目

在这里插入图片描述

解答
  1. 法一:单循环链表

    	#include<stdio.h>
    	#include<stdlib.h>
    	
    	//定义单循环链表数据结构
    	typedef struct CNode{
    		int data;
    		struct CNode *next;
    	}CNode;
    	typedef CNode *CLinkList;
    	
    	//初始化一个指向头节点的指针,使头指针->next=头指针,头指针->data不做处理
    	void InitList_L(CLinkList *L){
    		(*L) = (CLinkList)malloc(sizeof(CNode));
    		if(!(*L))
    			exit(-2);
    		(*L)->next = *L;  
    	}
    	
    	//头插法建立单循环链表
    	int CreateList_HL(CLinkList *L,int s[],int n){ //二级指针
    		int i;
    		CLinkList p;
    		*L = (CLinkList)malloc(sizeof(CNode));
    		if(!(*L))
    			exit(-2);
    		(*L)->next = *L;			//只有一个头节点head,就让next指向自己
    		for(i=0; i<n; i++){
    			p = (CLinkList)malloc(sizeof(CNode));
    			if(!p)
    				exit(-2);
    			p->data = s[i];			//录入数据 
    			p->next = (*L)->next;   //将头指针所指向的下一个结点的地址,赋给新创建结点的next 
    			(*L)->next = p;		    //将新创建的结点的地址赋给头指针的下一个结点
    		}
    		return 1;
    	} 
    	
    	//尾插法建立单循环链表
    	int CreateList_TL(CLinkList *L,int s[],int n){
    		int i;
    		CLinkList p, q;	
    		*L = (CLinkList)malloc(sizeof(CNode));
    		if(!(*L))
    			exit(-2);
    		(*L)->next = *L;	//只有一个头节点head,就让next指向自己
    		for(i=0,q=*L; i<n; i++){
    			p = (CLinkList)malloc(sizeof(CNode));
    			if(!p)
    				exit(-2);
    			p->data = s[i];	//录入数据 
    			q->next = p;
    			q = q->next;
    		}
    		q->next = *L;      //最后一个结点指向head
    		return 1;
    	}
    	
    	//求单循环链表的长度
    	int ListLength(CLinkList L){
    		CLinkList p;				
    		int count;
    		if(L){
    			count = 0;
    			p = L;			   //p指向头结点  
    			while(p->next!=L){ //p没到表头
    				count++;
    				p = p->next;
    			}
    		}
    		return count;
    	} 
    	
    	//得到指向单循环链表第i个元素的指针
    	CLinkList GetElemPtr(CLinkList L, int i){										
    		int count;
    		CLinkList p;
    		if(L&&i>0){
    			count = 1;
    			p = L->next;
    			while(p!=L && count<i){
    				count++;
    				p = p->next;
    			}
    			if(p!=L)   //L为头指针
    				return p;
    		}
    		return NULL;
    	}
    	
    	//单循环链表第i个位置插入元素e
    	int ListInsert(CLinkList L, int i, int e){
    		CLinkList p, s;
    		int j;
    		if(i<1 || i>ListLength(L)+1)	//插入位置不合理
    			return 0;
    		p = L;
    		j = 0; 
    		while(j<i-1){  //寻找第i-1个结点
    			p = p->next;
    			++j;
    		}
    		s = (CLinkList)malloc(sizeof(CNode));
    		if(!s)
    			exit(-2);
    		s->data = e;
    		s->next = p->next;
    		p->next = s;
    		return 1;		
    	} 
    	
    	//单循环链表第i个位置删除元素e
    	int ListDelete(CLinkList L, int i, int *e){
    		CLinkList pre, p; 
    		int j;
    		if(i<1 || i>ListLength(L))	//删除位置不合理
    			return 0;
    		pre = L;
    		j = 1; 
    		while(pre->next && j<i){  //寻找第i个结点,并令pre指向其前驱 
    			pre = pre->next;
    			++j;
    		}
    		p = pre->next;
    		pre->next = p->next;
    		*e = p->data;
    		free(p);
    		p=NULL;
    		return 1; 
    	}
    	
    	//遍历单循环链表
    	void ListTraverse(CLinkList L){
    		CLinkList p;
    		p = L->next;	  //p指向头结点,正向访问链表
    		while(p!=L){
    			printf("%d ",p->data);
    			p = p->next;
    		}
    		printf("\n");
    	}
    	
    	//判断单循环链表是否为空
    	int ListEmpty(CLinkList L){
    		if(L!=NULL && L->next==L)  //单循环链表存在并且只有头结点 
    			return 1;
    		else
    			return 0;
    	}
    	
    	/*
    	用单循环链表解决约瑟夫环问题(带头结点)
    	这里的单循环链表使用了带头结点的,方便找到表头的位置,但是由于头结点不存储元素,因此需要跳过头结点
    	链表插入删除都比较方便,不需要移动大量元素,只需要移动指针即可,适合这一类问题
    	*/
    	void Algo(CLinkList L,int k,int m){ //从编号为k的人开始数,数到m的那个人出队列
    		int count,i,j;  //count表示每次从1开始数,i用来找编号为k的结点的前驱
    		CLinkList pre,p;
    		pre=L;
    		count=1,i=1,j=1; 
    		/*寻找第k个结点,并令pre指向其前驱*/
    		while(i<k){       
    			if(pre->next==L) //跳过头结点
    				pre=pre->next->next;
    			else
    			    pre = pre->next;
    			++i;
    		}
    		while(L->next!=L){  //如果单循环链表不为空
    			if(count==m){
    				/*下一个结点不是头结点,直接删除*/
    				if(pre->next!=L){  
    					p=pre->next;
    					printf("第%d次出环的元素是%d\n",j++,p->data);
    					pre->next=p->next;
    					free(p);
    					p=NULL;
    					count=1;
    				}
    				/*下一个结点是头结点,下下个结点不是头结点,跳过头结点,删除下下个结点(头结点不保存数据,因此不参与运算)*/
    				else if(pre->next->next!=L){ 
    					p=pre->next->next;
    					printf("第%d次出环的元素是%d\n",j++,p->data);
    					pre->next->next=p->next;
    					free(p);
    					p=NULL;
    					count=1;
    				}
    				/*下一个结点是头结点,下下个结点也是头结点,说明单循环链表已经为空了,只剩下头结点,因此跳出循环*/
    				else  
    					break;
    			}
    			count++;        //count代表要删除的结点数的数,始终在pre指向的结点之前
    			if(pre->next==L) //跳过头结点
    				pre=pre->next->next; //pre指向要删除结点的前一个结点
    			else
    				pre = pre->next;     //pre指向要删除结点的前一个结点
    		}
    	}
    
    	void main(){
    		CLinkList L;
    		int n=5,s[5]={1,2,3,4,5}; //假设5个人围坐一圈
    		int k=3,m=2;              //第一次从编号为k的人开始数,数到m的那个人出队列
    		CreateList_TL(&L,s,n);    //头插法建立单循环链表
    		ListTraverse(L);          //遍历原始队列
    		printf("假设第一次从编号为%d的人开始数,数到%d的那个人出环\n",k,m);
    		Algo(L,k,m);       //用单循环链表解决约瑟夫环问题(带头结点)
    	}
    
  2. 法二:循环数组

    	/*
    	用循环数组解决约瑟夫环问题
    	解决问题的难点有两个:
    	1、如何求下一个的人的位置:在循环数组中向后移动采用:(k+l)%n
    	2、此人出圈后对这个人的位置如何处理:先将出圈人的位置打印输出,然后将其位置元素设置为0,表示它已出圈,以后见到它就直接跳过去
    	*/
    	void Algo2(int s[],int n,int k0,int m){  //开始一共有n个人,从第k0个人开始数,数到m的那个人出队列
    		int i,j,k=k0-1;     //元素访问的下标为 k,初始时从第k0个人开始数
    	    for(i=0;i<n;i++){   //总共循环n次,每循环一次,出队一个元素,k在循环中会不断的运动
    	        j=1;            //j表示数的数,初始为1
    	        while(j<m){     //如果还没有数到m
    	            while(s[k]==0) //如果s[k]为0,证明它已经出圈了,则跳过它
    					k=(k+1)%n; 
    	            j++;         //数下一个数
    	            k=(k+1)%n;   //数组下标向后走一步
    	        }
    	        while(s[k]==0) //如果数到m发现它出圈了,则跳过它,找到第一个没出圈的数
    				k=(k+1)%n;
    	        printf("第%d次出环的元素是%d\n",i+1,s[k]); //先将出圈人的位置打印输出
    	        s[k]=0; //然后将其位置元素设置为0,表示它已经出圈
    	    }
    	}
    
    	void main(){
    		int n=5,s[5]={1,2,3,4,5}; //假设5个人围坐一圈
    		int k=3,m=2;              //第一次从编号为k的人开始数,数到m的那个人出队列
    		printf("假设第一次从编号为%d的人开始数,数到%d的那个人出环\n",k,m);
    		Algo2(s,n,k,m);    //用循环数组解决约瑟夫环问题
    	}
    
  3. 法三:递归

    	/*
    	用递归解决约瑟夫环问题
    	n指的是总人数,m指的是每次最大报到的数值,i是第i次,该函数每次可以求出第i次扔海里的人的编号
    	*/
    	int Algo3(int n,int m,int i){
    		if(i==1)
    			return (n+m-1)%n;
    		else
    			return (Algo3(n-1,m,i-1)+m)%n;
    	}
    
    	void main(){
    		int n=5; //假设5个人围坐一圈
    		int m=2; //数到2的那个人出环             
    		printf("假设第一次从编号为1的人开始数,数到%d的那个人出环\n",m);
    		for(int i=1;i<=n;i++)
    			printf("第%d次出环的元素是%d\n",i,Algo3(n,m,i)+1); //因为求出的结果是数组中的下标,最终的编号还要加1
    	}
    
  4. 法四:迭代

    	/*
    	用迭代解决约瑟夫环问题
    	递推公式:
    	f(N,M)=(f(N−1,M)+M)%N
    	f(N,M)表示,N个人报数,每报到M时杀掉那个人,最终胜利者的编号
    	f(N−1,M)表示,N-1个人报数,每报到M时杀掉那个人,最终胜利者的编号
    	*/
    	int Algo4(int n,int m){
    		int i,p=0;
    		for(i=2;i<=n;i++){
    			p=(p+m)%i;
    		}
    		return p+1; //因为求出的结果是数组中的下标,最终的编号还要加1
    	}
    
    	void main(){
    		int n=5; //假设5个人围坐一圈
    		int m=2; //数到2的那个人出环
    		printf("假设第一次从编号为1的人开始数,最后出环的是:%d\n",Algo4(n,m));
    	}
    
  • 11
    点赞
  • 109
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值