模拟 - 约瑟夫环问题及其变式

本文介绍了约瑟夫斯问题及其变种约瑟夫环,详细解析了问题背景及算法实现,包括递归与非递归版本的实现方式,并探讨了优化方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

约瑟夫斯问题


 

约瑟夫斯问题(有时也称为约瑟夫斯置换),是一个出现在计算机科学数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环

有 n  个囚犯站成一个圆圈,准备处决。首先从一个人开始,越过 k − 2  个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过 k − 1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。

问题是,给定了 n 和 k  ,一开始要站在什么地方才能避免被处决?


————源自维基百科



比较简单的做法是用循环单链表模拟整个过程,时间复杂度是O(n*m)。如果只是想求得最后剩下的人,则可以用数学推导的方式得出公式。

公式推导如下:





#include <iostream>
using namespace std;
//編號從0開始,也就是說如果編號從1開始結果要加1
int josephus(int n, int k) { //非遞回版本
	int s = 0;
	for (int i = 2; i <= n; i++)
		s = (s + k) % i;
	return s;
}
int josephus_recursion(int n, int k) { //遞回版本
	return n > 1 ? (josephus_recursion(n - 1, k) + k) % n : 0;
}
int main() {
	for (int i = 1; i <= 100; i++)
		cout << i << ' ' << josephus(i, 5) << ' ' << josephus_recursion(i, 5) << endl;
	return 0;
}


维基百科所给的解释已足够清楚,在此仅作学习笔记。

以下是约瑟夫问题的变式:

约瑟夫环

时间限制: 1 Sec   内存限制: 128 MB

题目描述

约瑟夫的问题是非常出名的。从N个人中,编号为1,2,。。,N,站成一圈报数,每个报到M的人都会被枪决,只有最后剩下的人能够活命。约瑟夫很聪明,他选择了活命者的位置,给我们留下了这个故事。
例如,当N=6,M=5,那么被杀的顺序是5,4,6,2,3,1
现在假设有k个好人和k个坏人。在圈内的前k个是好人,后k个是坏人。
请你确定一个最小的m,使得好人被杀前,坏人全部都被杀掉。

输入

每一行输入一个k值,输入为0时,结束(0<k<14)

输出

输出能够满足的M

样例输入

3
4
0

样例输出

5
30



这个问题我最开始选择的是链表的模拟算法,如下:

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<iomanip>
#include<cstdlib>
#include<algorithm>
#include<string>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<list>

using namespace std;


const int N = 2500000;

int res[14] = { 0,2,7,5,30,169,441,1872,7632,1740,93313,459901,1358657,2504881 };
void solu()
{
	list<int> joesph;
	list<int>::iterator it;
	int m = 0, n = 0;
	for (int k = 1; k < 14; k++)
	{
		n = k * 2;
		int flag = 1;
		for (m = n / 2 + 1; flag && m < N; m++)//从小的数开始遍历,找到即可以退出
		{
			if (!(m % (k + 1) == 0 || m % (k + 1) == 1))continue;
			//cout << "当杀人方法为杀第" << m << "个人时" << endl;
			joesph.clear();
			for (int i = 1; i <= n; i++)//初始化列表
				joesph.push_back(i);
			it = joesph.begin();
			for (int i = 1; i < m; i++)//报数
			{
				it++;
				if (it == joesph.end())it = joesph.begin();//循环操作
			}
			while (*it > n / 2)//开始杀好人时结束循环
			{
				//cout << "杀了序号为" << *it << "的人" << endl << endl;
				it = joesph.erase(it);
				if (it == joesph.end())it = joesph.begin();//循环操作
				for (int i = 1; i < m; i++)//报数
				{
					it++;
					if (it == joesph.end())it = joesph.begin();//循环操作
				}
			}
			flag = 0;
			for (it = joesph.begin(); it != joesph.end(); it++)
			{
				if (*it > n / 2)flag = 1;//如果还有坏人就标记,进行下一个循环
			}
		}
		/*for (it = joesph.begin(); it != joesph.end(); it++)
		{
			cout << *it << endl;
		}*/
		res[n] = m - 1;
		cout << n << " " << m - 1 << endl;
	}
}
int main()
{
	solu();
	int k;
	while (cin >> k, k)
		cout << res[k] << endl;
	return 0;
}



但是很明显,这个方法耗时太长,才到7或者8就需要几秒钟到几十分钟。所以这种方法是不太合适的。这就需要用到数学的推导方法。

根据之前的数学推导,可以得到:


#include<stdio.h>

int ans[14] = { 0 };

int joseph(int k)
{
	int cnt, p;
	if (ans[k]) return ans[k];
	for (int i = k + 1;; i++)
	{
		for (cnt = 2 * k, p = 0; cnt>k; cnt--)        //检查是否满足要求
		{
			p = (p + i - 1) % cnt;            //被杀掉的人的位置
			if (p < k) cnt = 0;                //被杀掉的人是前k个(好人)
		}
		if (cnt == k)                          //所有坏人都被杀了,满足要求
		{
			ans[k] = i;
			return i;
		}
	}
	return 0;
}

int main()
{
	int n;
	while (scanf("%d", &n) != EOF && n)
	{
		printf("%d\n", joseph(n));
	}
	return 0;
}



参考与转载来源:

https://zh.wikipedia.org/wiki/%E7%BA%A6%E7%91%9F%E5%A4%AB%E6%96%AF%E9%97%AE%E9%A2%98

http://blog.youkuaiyun.com/u013446688/article/details/43058247

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值