约瑟夫环问题

模拟约瑟夫环问题

循环数组模拟

经典算法–约瑟夫环问题的三种解法

问题描述:N个人围成一圈,从第一个人开始从1开始报数,报到m的人出圈,剩下的人继续从1开始报数,报到m的人出圈;如此往复,直到所有人出圈。(模拟此过程,输出出圈的人的序号)

在这里插入图片描述
循环的开始和结束:循环的结束取决于圈内是否还有“人”,可以用一个变量alive表示初始人数,每一次出圈,alive - 1。判断alive是否非0即可。

while(alive > 0)

每一次循环,就是“过”一个人,但是,这个人有两种不同的状态:在圈内和不在圈内;在圈内就报数,number+1,不在圈内就不参与报数,number不变。

假设有N个int元素的数组,每一个int元素表示一个“人”;并且,取值为0和1, 1表示在圈内,0表示不在圈内,所以,如果这个人在圈内,number + 1;如果这个人不在圈内,number + 0。那么,在报数的时候,不需要考虑这个人在不在圈内(每一个人都需要加1或加0,所以,可以在这块优化一下程序)。

void joseph(int count, int doom) {
	int alive = count;		//幸存人数 
	int number = 0;			//计数,当number==doom时,淘汰这个人 
	int index = 0;			//下标,为总人数-1 
	int *circle = NULL;		//根据需求设为循环数组,存储每个人 
 
	//用calloc()函数申请得到的空间,自动初始化每个元素为0
	//所以,0表示在这个人在约瑟夫环内,1表示这个人出圈,即“淘汰” 
	circle = (int *) calloc(sizeof(int), count);
 
	//只要幸存人数大于0,则一直进行循环 
	while(alive > 0) {
		number += 1- circle[index];	//每轮到一个人报数,不管是"0"还是"1"都进行计数 
		if(number == doom) {		//当number==doom时,就要淘汰当前这个人
			/*
				淘汰一个人需要做四步操作:
					1、输出这个人的位置 
					2、把这个人的状态从在圈内"0"改为不在圈内"1" 
					3、幸存人数alive-- 
					4、 计数器number归零 
			*/ 
			alive == 1 ? printf("%d", index+1) : printf("%d,", index+1);
			circle[index] = 1;
			alive--;
			number = 0;
		}
		//与总人数count取余,则可以使index在0~count-1之间 一直循环,达到循环数组的目的 
		index = (index +1) % count;	
	}
	printf("\n");
 
	free(circle);		//结束后一定要释放circle所申请的空间 
}

链表模拟

数学分析

初始情况
一共n个人(编号0,1,2… n-1),组成一个环。第一个人从1开始报数,报数到m的人出环,然后,下一个人重新开始从1开始报数,直到环中只有一个人。

分析:
第一个出环的人编号是 (m-1)%n,设之为(k-1)

剩下的n-1个人组成了一个新的约瑟夫环(以编号为k==m%n的人开始):

k k+1 k+2 … n-2, n-1, 0, 1, 2, …,k-3, k-2

现在我们把他们的编号做一下转换:

x’ -> x

k --> 0
k+1 --> 1
k+2 --> 2


k-2 --> n-2
k-1 --> n-1

变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗!

x ->x'?(这正是从n-1时的结果反过来推n个人时的编号!)

0 -> k
1 -> k+1
2 -> k+2


n-2 -> k-2

变回去的公式 x’=(x+k)%n

那么,如何知道(n-1)个人报数的问题的解?只要知道(n-2)个人的解就行了。(n-2)个人的解呢?只要知道(n-3)的情况就可以了 ---- 这显然就是一个递归问题:

令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果就是f[n]

递推公式

f[1]=0;

f[i]=(f[i-1]+k)%i = (f[i-1] +m%i) % i = (f[i-1] + m) % i ; (i>1)

1.递归:

#include <stdio.h>
int josephus(int n, int m) {
	if(n == 1) {
		return 0;
	}
	else {
		return (josephus(n-1, m) + m) % n;
	}
}
 
int main() {
	int n, m;
	while (scanf("%d", &n) == 1) {
		if (!n) {
			break;
		}
		scanf("%d", &m);
		int result = josephus(n, m);
		printf("%d\n", result+1);
	}
	return 0;
}

2.迭代:

#include <stdio.h>
int main() {
	int n, m, i, result;
	while (scanf("%d", &n) == 1) {
		if (!n) {
			break;
		}
		scanf("%d", &m);
		result = 0;
		for (i = 2; i <= n; i++) {
			result = (result + m) % i;
		}
		printf("%d\n", result + 1);
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值