C语言实现约瑟夫问题(链表、数学取模方式)

目录

问题描述

单向循环链表处理

单向循环链表处理具体代码

数学取模方式处理

数学取模方式处理具体代码


问题描述

一堆猴子都有编号,编号是1,2,3 ...m,这群猴子(m个)按照1-m的顺序围坐一圈,从第K开始数,每数到第N个,该猴子就要离开此圈,这样依次下来,直到圈中只剩下最后一只猴子,则该猴子为大王。


单向循环链表处理

  •         用单向循环链表模拟猴子围成圈坐,如下图所示

 first指向第一个报数的猴子,每报一个数first指向它的next,直到停在需要出圈的猴子位置

  •         当要编号为2的猴子要出圈时,操作如下:

①  first指向当前要删除结点,helper指向要删除结点的后一个结点,同时将first赋值给temp

        

②  first指向要删除结点的下一个结点

③  helper的next指向first

④ 释放要删除结点 


单向循环链表处理具体代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>

/* 约瑟夫问题——循环单向链表解决(不带头结点) */

//定义链表结点
typedef struct Node{
	int no;  //编号
	struct Node *next;  //指向下一个链表结点的指针 
}CircleSLNode; 

//创建一个单向循环链表 
CircleSLNode *CreateList(int nums) {
	//first: 单向循环链表 第一个结点,curnode:辅助遍历,node:新的结点 
	CircleSLNode *first = NULL, *curnode = NULL, *node = NULL;
	int i;
	if(nums < 1){
		printf("猴子个数输入不正确!\n");
		return NULL;
	}
	
	first = (CircleSLNode *)malloc(sizeof(CircleSLNode));  //指向第一个结点 
	for(i = 1; i <= nums; i++){
		node = (CircleSLNode *)malloc(sizeof(CircleSLNode));  //根据编号创建猴子结点
		node->no =  i;
		//如果是第一只猴子
		if(i == 1){
			first = node;  //first 指向 第一个结点 
			first->next = first;  //构成环 
			curnode = node;
		} else{
			curnode->next = node;  // curnode是当前环形链表最后一次添加的结点,让 curnode->next 指向 node 
			//node->next = first;  //node->next 指向 first ,构成环 
			curnode = node;  //curnode后移 
		}
	} 
	
	curnode->next = first;
	return first;
}

//猴子出圈,返回留下来的第n只猴子 
/*
* first:要穿过来的单链表 
* startnum:开始报数的猴子编号 
* nums:全部猴子个数
* countnums:数到第 countnums 出圈 
*/
int findout(CircleSLNode *first, int startnum, int nums, int countnums){
	//设置帮助指针helper,temp, 帮忙 出圈
	CircleSLNode *helper = first, *temp = NULL;
	int flag = 1, i;
	 
	//判断传过来的参数是否符合要求
	if(first == NULL || startnum < 1 || nums < 1 || startnum > nums){
		printf("参数输入错误!\n");
		return 0;
	} 
	
	//将helper移到first的前一个结点 
	while(flag){
		if(helper->next == first){
			flag = 0;
		}else{
			helper = helper->next;
		}
	}
	
	//猴子报数前,先把指针移到开始报数的猴子结点,移动startnum - 1次 
	for(i = 0; i < startnum - 1; i++){
		first = first->next;  //最后first指向 开始报数的猴子结点
		helper = helper->next;  //保证跟在first后面 
	} 
	
	//当猴子开始报数时,让 first , helper 移动 countnums - 1 次,直到圈中只有一个结点 
	flag = 1;
	while(flag){
		if(helper == first){
			flag = 0;
		}else{
			for(i = 0; i < countnums - 1; i++){
				first = first->next;  //最后first指向 要出圈的猴子结点
				helper = helper->next;  //保证跟在first后面 
			}
			printf("猴子 %d 出圈\n", first->no);
			//将first指向的猴子结点出圈
			temp = first;
			first = first->next;
			helper->next = first;
			free(temp);  //释放出圈结点内存 
			temp = NULL;
		}
		
	} 
	
	printf("最后称王的是第 %d 只猴子\n", first->no);
	return first->no; 
} 

//撤销单链表
void Destroy(CircleSLNode **first, int nums){
	//定义辅助指针 ,head暂时不能没有,故temp1指向head,temp2配合释放掉 
	CircleSLNode *temp1 = NULL, *temp2 = NULL;
	int i;
	
	//定义temp1辅助遍历 
	temp1 = *first;
	
	for(i = 1; i <= nums; i++){  //最终:temp1 = NULL,temp2被释放 
		temp2 = temp1;   //temp2指向temp1,便于释放 
		temp1 = temp1->next;  //temp1指向下一个,避免链表丢失 
		free(temp2);  //释放temp2 
		temp2 = NULL;  //避免free都成为“野指针” 
	}
	*first = NULL;  //最后让head = NULL 
}

//测试 
int main(int argc, char *argv[]) {
	CircleSLNode *first = NULL;
	int nums= 0, startnums = 0, countnums= 0;
	
	printf("请输入想要称王的猴子个数: ");
	scanf("%d", &nums); 
	
	printf("从第几个人开始报数:");
	scanf("%d", &startnums);
	
	printf("要出圈的数: ");
	scanf("%d", &countnums);
	
	//创建单链表
	first = CreateList(nums);	
	
	//test: 
	//共六个人(nums=6),从第一个人开始报数(startnum=1),数到5的出圈(countnums=5),出圈的顺序是:5,4,6,2,3
	findout(first, startnums, nums, countnums);
	
	//撤销单链表
	Destroy(&first, nums);
	return 0;
}

数学取模方式处理

默认从第一只猴子开始报数。


数学取模方式处理具体代码

#include <stdio.h>
#include <stdlib.h>

/* run this program using the console pauser or add your own getch, system("pause") or input loop */

int main(int argc, char *argv[]) {
	int nums, countnums, i, s = 0;
	printf("请输入想要称王的猴子总数: ");
	scanf("%d", &nums);
	printf("请输入要出圈的数字: "); 
	scanf("%d", &countnums);
	for(i = 2; i <= nums; i++)
		s = (s + countnums) % i;
	printf("称王的猴子是:%d\n", s + 1);
	return 0;
}

本篇参考:

数组模拟参考:尚硅谷Java数据结构与java算法(Java数据结构与算法)

数学取模方式处理解析为原创,请读者转载时附上本篇链接!谢谢!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值