用c语言解决约瑟夫环问题

       问题引入

        约瑟夫环是一种经典的益智问题,一个圈共有N个人(N为不确定的数字),第一个人的编号为0或者1(两个都可以,看你的程序如何编写),假设这边我将第一个人的编号设置为1号,那么第二个人的编号就为2号,第三个人的编号就为3号,第N个人的编号就为N号,现在提供一个数字M,第一个人开始从1报数,第二个人报的数就是2,依次类推,报到M这个数字的人出局,紧接着从出局的这个人的下一个人重新开始从1报数,和上面过程类似,报到M的人出局,直到N个人全部出局,请问,这个出局的顺序是什么?

事例分析

        我们先看一组例子分析。假设有10个人,每报到3的人出局,那么结果是什么呢?

然后报到3的人出局变成下图

然后开始下一轮报数

继续循环

当到末尾时再返回第一个成环报数

以此类推 ,随后出局的人是7 1  8 5 10 最后剩的人是4。

        这样看起来逻辑不是特别复杂,我们便有了第一种用数组模拟环进行运行结果。

解决方案一

        这种方案主要采用循环判断的方法一个个找出出局人。

#include<stdio.h>
int main()
{
	int arr[100] = { 0 };
	int peo = 0, n = 0;
	int i = 0, count = 0, d = 0;

	scanf("%d %d", &peo, &n);

	for (i = 0; i < peo; i++)
	{
		arr[i] = i + 1;//初始化数组让里面元素为1,2,3便于后续操作
	}

	while (count < peo)//找到n人结束
	{
		i = i % peo;//让i的范围再不断循环假设有三人,0 1 2 0 1 2 0 1 2

		if (arr[i] != 0)
		{
			d++;
		}
		if (d == 3)
		{
			printf("%d ", arr[i]);
			arr[i] = 0;
			d = 0;
			count++;
		}
		i++;//每次对i进行加一
	}

	return 0;
}

        

        这充分运用了取余形成循环的特点,也是比较通俗易懂的方式。下面然我们看看方案二,一种神奇的规律。

解决方案二(神奇的规律)

        让我们把视角再返回到第一个事例,10个人报倒3出局。(以下红色为新序列排号,黑色为最初排号)

我们第一次数到三,显然我们知道是第三个人,即3,它可以表达为  3%10=3.第二个人是什么样子呢?

是第6个人,此时他的序号因为消失一人变成5, 是不是可以表达为  (3-1+3)%9=5,这里为什么是9而不是10,答案十分简单,因为此时一共也只有9人,没有10人。再次继续下一循环。

此时出局的人是第九人,那么他是不是可以模仿前面写成  (5-1+3)%8=7即(i-1+n)%m(i 前一个出局数的序号,初始为0;n要报到出局的数; m当前总人数)到了这里相信你与我最初发现它一样,激动于这是不是一条普遍规律。我们接着来验证。

出局人是第二人,我们接着写。  (7-1+3)%7=2,对于环形我们第一个循环问题显然也是可以的。 继续操作。

同理可写   (2-1+3)%6=4,我们接着操作。

 同理可写   (4-1+3)%5=1,继续

        

 同理可写   (1-1+3)%4=3,这个公式同样适用

接着下一个

   同理可写   (3-1+3)%3=2

 同理可写   (2-1+3)%2=0此时我们发现10的下标不是0,而是2,我们不难发现此时要出局的人位于队尾,刚好是m的整数倍,摸m之后为0,因此我们对这种情况要特别处理。(i-1+n)%m(i 前一个出局数的序号,初始为0;n要报到出局的数; m当前总人数)如果计算结果为0,就输出序号为m的数。也只有他符合取余为0.此时的 i应当为1.

        

(1-1+3)%1=0,我们输出队尾元素。即4.

基于以上分析,我们可以写出如下代码。

#include <stdio.h>

int main()
{
	int arr[100] = { 0 };
	int i=0, n=0,peo=0 ,i2 = 0;
	int c = 0;
	scanf("%d %d", &peo, &n);

	for (i = 1; i <= peo; i++)
	{
		arr[i] = i;
	}//此时i=1开始初始化,是为了更好与我们发现的规律吻合
	//i=0开始,只需略微修改一下代码即可
	
	
	for (i = 0; i < peo; i++)
	{
		int tmp = (c + n)%(peo - i);
		//这里用i表示循环的次数peo - i正好是当前人数。
		//c初始为0,输出后为tmp - 1,对应原来的序列数减一。
		if (tmp == 0)
		{
			printf("%d ", arr[peo-i]);
			c = 0;
		}
		else
		{
			printf("%d ", arr[tmp]);
			c = tmp - 1;
			//此时我们要将出局人后面的人全都提前一位,去除空位存在。
			for (i2 = tmp; i2 < peo - i; i2++)
			{
				arr[i2] = arr[i2 + 1];
			}
		}
	}

	return 0;
}

        相信你看完上述代码与分析可以明白这个循环的意义。

  

   方案二的循环看起来有些繁琐,当我们学完链表时,可以用链表优化这个循环。期待以后我们可以一起优化吧。

        到这里这篇文章就结束了,感谢坚持看到这的读者。大家如果喜欢就点点赞收藏吧!期待我们下次再见。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值