约瑟夫环问题,n个人围成一圈,依次按1、2.....m来报数,报数值为m的人出圈,求最后出圈的人和出圈的序列

约瑟夫环问题(有时也称为约瑟夫斯置换,是一个出现在计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。又称“丢手绢问题”.),也就是如下图这个样子:

在这里插入图片描述
简单来讲就是n个人围成一圈,依次按1、2…m来报数,报数到m的出圈,当第一个人出圈后,那么出圈的下一个人则又按1、2…m来报数,如此在一个圈内循环报数,直到全部的人都出去圈。

首先我们定义一个数组num以及n,m; n的数值是圈子人的个数,num数组是用来存储每个人的编号如:1、2、3、4…n,而m则是报数值;比如当j报数值为m时,将其num[j]的值赋为0表示已经出圈,后面再次循环时便通过判断跳过值为0的。
废话不多说先放出主函数的代码部分:

#include <stdio.h>
#define N 100


int main() {

	int num[N];
	int n, m;
	printf("请输入总人数和报数值:");
	scanf("%d%d", &n, &m);
	
	//给每个数组赋上编号的值
	for(int i = 0; i < n; ++i) {
		num[i] = i+1;
	}
	
	printf("依次出环的顺序为:");
	
	//josef就是实现的关键函数,函数有一个返回值返回最后出圈的人的编号值,在函数内也实现了打印每个出队的编号
	int final = josef(num, n, m);
	
	printf("\n最后出的人为原先的%d号", final);
    return 0;
}

下面给出josef函数的实现:

int josef(int num[],int n,int m)
{
	//定义最后一个出圈人的编号值final
	int final;

	int count, i , j = 0;
	
	
	//最外层的循环:每一次循环便实现出圈一个人,直到全部出圈,并且每次将出圈人的编号打印出
	for(count = 0; count < n; ++count) {
		i = 1;
		
		// 每次while一次循环表示报一次数,i来记录报数值,当遇到报数值为m的时候跳出循环
		while(i < m) {
			while(!num[j])   j=(++j)%n; //判断是否为0
			
			i++;
			j = (++j)%n;//
		}
		
		while(!num[j])   j=(++j)%n; 
		
		printf("%d ", num[j]);
		if(count == n-1) {
			final = num[j];	
		}
		//在设置为0的之前,先将其打印出,并且判断是不是最后一个出圈
		
		num[j] = 0;
	}
	return final;
}

比如我我们输入n和m的值分别为 53 来模拟一次输出:
在这里插入图片描述
一次循环后(置为0的表示已出圈):
在这里插入图片描述
每循环一次置报数为3的为0, 当到第三次循环时,由2开始从1报数:
在这里插入图片描述
直到后面第4、5次循环结束都为0,最后输出结果就是:
在这里插入图片描述
比如我们再输入100 9:
在这里插入图片描述

<< 这是一个经典的约瑟夫环问题的变种版本。我们可以使用循环链表或STL中的`std::list`来模拟这个过程。 下面是基于C++ STL `std::list` 的解决方案: ```cpp #include <iostream> #include <list> int main() { int n, m; std::cin >> n >> m; // 创建一个列表存储每个人的编号 std::list<int> people; for (int i = 1; i <= n; ++i) { people.push_back(i); } // 存储每个人的密码 int keys[n]; for (int i = 0; i < n; ++i) { std::cin >> keys[i - 1]; // 注意这里要减去已经出队的数,所以先存起来再用索引取 } auto it = people.begin(); // 初始化迭代器指向第一个人 while (!people.empty()) { // 当还有未被移除时 // 找到将要删除的位置 for (int count = 1; count < m; ++count) { if (++it == people.end()) it = people.begin(); } // 输出即将离开圈中的,并记录其位置 int removedPerson = *it; std::cout << removedPerson << " "; // 移动迭代器避免非法访问并更新新的起点 if (++it == people.end()) it = people.begin(); // 删除当前节点(即这个人) people.remove(removedPerson); // 更新新轮次的密钥m if(!people.empty()){ // 根据的原始顺序找到对应的key(因为删掉后序列会变化,需提前保存keys数组) --m; // 先--是因为之前加了一步才导致越界了 m = keys[removedPerson - 1]; } } return 0; } ``` ### 解释: 1. **数据结构选择**: 使用`std::list`容器是因为它支持快速地在任意位置插入删除元素,这非常适合模拟圆圈的操作。 2. **初始化逻辑**: 首先把所有参与游戏的都加入到链表当中,按序号依次排列好以便操作。 3. **核心算法步骤**: - 利用双层for循环控制报数字的过程;外层保证每一轮都有至少一出局直至空无一为止。 - 内部根据传入参数m决定具体谁会被踢出去以及调整下一个开始计数点位。 - 每当某成员被淘汰之后立即将他从队伍里抹消,并且把他的私钥作为新一轮的基础数重新定义规则。 4. **边界条件处理** : 在每次完成一轮淘汰动作以后都要确保还剩下足够数量玩家可供继续比赛,同时考虑到可能存在的特殊情况例如只有一名竞争者时直接宣布获胜即可结束程序运行。 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值