关于约瑟夫环问题解决

关于约瑟夫环问题解决


约瑟夫环问题介绍:

已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。

例如:n = 9, k = 1, m = 5

【解答】

出局人的顺序为5, 1, 7, 4, 3, 6, 9, 2, 8。
  再例如:有10人,淘汰顺序如图所示:
在这里插入图片描述


方法一:普通代码利用循环和标记解决

思考:
  • (1)如何实现出局?
  • (2)如何实现循环报数?
  • 对于(1)问题可以采用标记法,可以利用数组里面的元素代表人数,下标代表人物的序号,让没出局的元素等于0,如果出局该元素标记为1。
    那如何将元素为1的人排除?
    可以利用if语句if(arr[i]==0)则进入报数,再用报数判断是否出局,如下代码:
if (arr[i] == 0)
		{
			baoshu++;
			if (baoshu % M == 0)
			{
				arr[i] = 1;
				printf("%d出局\n", i + 1);//i+1代表人物序号(1,2,3,4...),而i指的是数组下标(从0开始0,1,2,3,4...)
			}
		}

当arr[i]=1时,该i代表的人物不再进入循环,实现了i代表人物的出局,arr[i]=1时,该i代表的人物可以继续进入报数。

  • 对于(2)问题可以利用while循环参考以下代码:
int i = -1;
	while (i < N)
	{
		i++;
		if (i == N)
		{
			i = 0;
		}
	}

此代码可以实现重复循环报数(N为总人数),那如何退出循环?
可以考虑统计出局人数,若出局人数 = 总人数 - 1,时可以停止循环并打印最后的人的序号。
完善报数代码(加入变量y,并判断y,考虑是否跳出循环):

if (arr[i] == 0)
		{
			baoshu++;
			if (baoshu % M == 0)
			{
				arr[i] = 1;
				printf("%d出局\n", i + 1);
				y--;
				if (y == 1)
				{
					break;
				}
			}
		}

最后可以参考笔者的代码:

#include <stdio.h>
#define N 10
#define M 3
int main()
{
	int arr[N] = { 0 };
	int i = -1;
	int baoshu = 0;
	int y = N;
	while (i < N)
	{
		i++;
		if (i == N)
		{
			i = 0;
		}
		if (arr[i] == 0)
		{
			baoshu++;
			if (baoshu % M == 0)
			{
				arr[i] = 1;
				printf("%d出局\n", i + 1);//打印被赋值为1的人,也就是出局的人
				y--;
				if (y == 1)//剩下最后一个人(元素依旧为0的那个)
				{
					break;
				}
			}
		}
		
	}
	for (i = 0; i < N; i++)//遍历,找出最后没有出局的人
	{
		if (arr[i] == 0)//最后一个人没有被赋值1
		{
			printf("最后剩下%d", i + 1);
		}
	}
	return 0;
}

方法二:利用递归函数找出出局人的序号

首先可以列表找规律
举例:有10个人报数,报到的数为N倍数的人则出局
  • 若N为3,则第3个人出局,这个不难理解,但如果N为11,那出局的人的序号为11吗?显然应该为1。那如何将这两种情况用一个公式表达呢?我们考虑用%。N %(这时候的总人数)= 出局人的序号
    3 % 10 = 3 11 % 10 = 1 ,%的作用其实就是起到连接作用。
  • 当N=3时,如图:可以从出局人后的一个人重新标号,当标号为3时,下一次出局的人就为该标号所指向的人。
    在这里插入图片描述
    每一列里第一行为原先序号,第二行为重新标号后的序号,我们不难发现,重新标的3号指向原先的号的人出局,且有关系:同一列里,原先序号 = 重新标号序号 + N(这里为3)前面提到N %(这时候的总人数)= 出局人的序号所以,这把出局的人的序号 = (重新标号后出局人的序号 + N(这里为3))%(这把的人数,也就是排序前人数);
  • 这次出局的人数可以由上一局出局人数推导而来,以此类推到第一局,上面说到过第一局可以由数学公式推导,可以找到递归出口:第一局,和递归方式
    参考以下代码:
#include <stdio.h>
int ysfh(int n, int N, int i)
{
	if (i == 1)
		return N%n;
	else
		return (ysfh(n-1,N,i-1)+N)%n;
}
int main()
{
	int n = 0, N = 0, i = 1;
	scanf("%d%d", &n, &N);
	for (i = 1; i <= n; i++)
	{
		printf("第%d次出局 %d\n", i, ysfh(n, N, i));
	}
	return 0;
}

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

  • 所以,==此方法有问题!!!==这里我并不是故意写出有问题的文章,而是作为初学者发现的问题,网上太多对约瑟夫环的介绍中多数人物序号为0,1,2,3,4,5,6…我当时认为如果序号从1开始是不是更直观,于是将序号变为1,2,3,4,5,6…于是问题就出现了,导致我卡了好久,终于找到了问题所在:
  • 如果人数为10,数到3出局,按原来思路3%10=3;第三个人出局,没问题;如果数到11者出局11%10=1,1号出局,没问题,但是我们忽略了如果数到10,10%10=0,根本没有0号人,于是我们需要从0开始标号才是正确的,从0开始标号才能利用%将报数连续起来。那既然思路没问题,那怎么改进原来代码?
  • 先看图:、在这里插入图片描述
  • 递归过程没问题(原先序号 = 重新标号序号 + N(这里为3))
    但是递归出口变了,第一次出局的人的序号应为(N-1)%(总人数10)= 2;
    所以只要修改递归出口即可
    如所示正确代码:
#include <stdio.h>
int ysfh(int n, int N, int i)
{
	if (i == 1)
		return (N-1)%n;//修改的地方
	else
		return (ysfh(n-1,N,i-1)+N)%n;//+N是由于原先序号 = 重新标号序号 + N
}
int main()
{
	int n = 0, N = 0, i = 1;
	scanf("%d%d", &n, &N);
	for (i = 1; i <= n; i++)
	{
		printf("第%d次出局 %d\n", i, ysfh(n, N, i));
	}
	return 0;
}

如果还没有理解可以尝试倒过来分析(任何第几次出局都可以由其他人数的第一局递推出)

如果弄懂此类问题,对递归函数的学习帮助是十分大的,有利于弄懂递归出口和递归条件,将复杂问题,简单化。当然约瑟夫问题还可以用数据结构解决,但笔者的知识有限,无法继续探究。

  • 11
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱写代码的刚子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值