约瑟夫问题求解及进一步思考

数组的抽象数据类型

数组(向量)是由同一种数据类型的数据元素组成的线性表.
数组是顺序存储的.
它是一种随机存储结构.

用类描述抽象数据类型-数组(向量),包括:
构造数组
插入 在向量第i个位置插入值为x的新结点,
删除 删除向量第i个元素

#include<iostream>
using namespace std;
enum boolean { FALSE, TRUE };
#define DefaultSize 100

template<class T>
class Vector
{
private:
	T* elements;
	int ArraySize, VectorLength;
	void GetArray(void);
public:
	Vector(int Size = DefaultSize);
	~Vector(void) { delete[] elements; }
	T Getnode(int i)
	{
		return(i < 0 || i >= VectorLength) ? NULL : elements[i];
	}
	boolean Insert(T& x, int i);
	boolean Remove(int i);
};

template<class T>
void Vector<T>::GetArray(void)
{
	elements = new T[ArraySize];
	if (elements == NULL) { cerr << "Memory Allocation Error" << endl; }
}

template<class T>
Vector<T>::Vector(int sz)
{
	if (sz <= 0)
		cerr << "Invalid Array Size" << endl;
	else
	{
		ArraySize = sz;
		VectorLength = 0;
		GetArray();
	}
}

template<class T>
boolean Vector<T>::Insert(T& x, int i)
{
	if (VectorLength == ArraySize)
	{
		cerr << "overflow" << endl; return FALSE;
	}
	else if (i<0 || i>VectorLength)
	{
		cerr << "position error" << endl; return FALSE;
	}
	else
	{
		for (int j = VectorLength - 1; j >= i; j--)
			elements[j + 1] = elements[j];

		elements[i] = x;
		VectorLength++;
		return TRUE;
	}
}

template<class T>
boolean Vector<T>::Remove(int i)
{
	if (VectorLength == 0)
	{
		cerr << "Vector is empty" << endl; return FALSE;
	}
	else if (i<0 || i>VectorLength - 1)
	{
		cerr << "position error" << endl; return FALSE;
	}
	else
	{
		for (int j = i; j < VectorLength - 1; j++)
			elements[j] = elements[j + 1];
		VectorLength--;
		return TRUE;
	}
}

约瑟夫问题

Josephus问题求解是数组(向量)结构的典型应用

设n个人围成一个圆圈,按一指定方向,从第s个人开始报数,每次数m下,报数为m的人出列,然后从下一个人开始重新报数,报数为m 的人又出列,……,直到所有的人出列为止。
Josephus问题要对任意给定的n,s和m,求按出列次序得到的人员顺序表。

问题求解分析

数学模型:用一个整数型向量记录n个人员的信息。
不妨用整数来为这n个人编号:1,2,n… , 并将这n个数存入一个数组P中,某个人出列即把对应的数组元素从数组中删除。每删除一个数组元素后,就将后面的所有元素前移,同时将这个删除的元素插到数组最后的位置上;然后对前n-1个数组元素重复上述过程。
当数组P中所有的元素都删去一次后,数组P中存放的就是报数出列的人员顺序。
模拟过程如下:

void Josephus(Vector<int>& P, int n, int s, int m)
{
	int k = 1;
	for (int i = 0; i < n; i++) { P.Insert(k, i); k++; }
	int s1 = s;
	for (int j = n; j >= 1; j--)
	{
		s1 = (s1 + m - 1) % j;
		if (s1 == 0) s1 = j;
		int w = P.Getnode(s1 - 1);
		P.Remove(s1 - 1);
		P.Insert(w, n - 1);
	}
}

该算法的时间消耗主要是删去数组元素后,剩下元素前移所用的时间,每次最多移动n-1个元素,总计最多移动次数n*(n-1),时间复杂度O(n^2)

约瑟夫问题的进一步思考(逆向求解)

问题: 假设你正在游玩约瑟夫游戏,从你开始报数,游戏规则与课上讲述一致,现在你想确保你是最后一个出列的玩家,如果由你设置m(即每报几个数出列一人),你应该如何设置m来确保自己的胜利?

思路一:

考虑到实际的约瑟夫问题中游戏参与者本着玩游戏的消遣态度和游戏顺利完成的实效性和游戏进行的高效性, 玩游戏的人数通常不会太多(极有可能不会超过100人),因此在已知约瑟夫正向算法的前提下, 可对报数m 进行穷举,直到符合条件输出m。
符合要求的m对应以下情况:从第s个人(即自己)开始报数m,最后剩下自己,即出列顺序表中最后一个存放的是s.
编写求解函数

//穷举法逆向求解
int findM(int n, int s)
{
	for (int i = 1; i <= n * n; i++) {  //穷举范围需要取合适大小
		Vector<int> P(n);
		Josephus(P, n, s, i);
		if (P.Getnode(n - 1) == s) return i;
	}
	return 0;
}

//检验穷举范围的合理性
int Effict(int max, int s) {
	int fail_m = 0;                 // max以内无解的n 的个数
	for (int i = 1; i <= max; i++) {
		if (findM(i, s) == 0) fail_m++;
	}
	return fail_m;
}

运行获取结果

int main(void)
{
	int n, s, m;
	cout << "Input total number, start index" << endl;
	cin >> n >> s;
	m = findM(n, s);
	//cout << m << endl << "输入max:  ";
	//int max;
	//cin >> max;
	//cout << "求不出m的数有    个" << Effict(max, s) << endl;
	Vector<int> P(n);
	Josephus(P, n, s, m);
	cout << "The index of last member is:" << endl;
	cout << P.Getnode(n - 1) << " ";
	return 0;
}

以下对穷举的范围做讨论:
例如对n=3, m最小为5时符合条件,所以穷举的范围比3本身要大;
现构造函数检验穷举范围的合理性:
当穷举的范围是 i<=n 时,100内有25个数得不到对应的m;
当穷举的范围是 i<=2n 时,100内有6个数得不到对应的m;
所以O(n)的穷举范围不够,所以设置 i<= n*n, 此时100内所有数都能得到对应的m

思路二:

下面将一种理想情况反向进行演绎:
假设自己是队列中的第一个人。

  • 当队列中还剩2个人时,需要将最后一个人(第2个人)出列;
    m _2= 2*i (i = 1, 2,…,N)满足条件
  • 当队列中还剩3个人时,需要将最后一个人(第3个人)出列;
    m _3= 3*i (i = 1, 2,…,N)满足条件
    ,
  • 当队列中还剩n个人时,需要将最后一个人(第n个人)出列;
    m _n= n*i (i = 1, 2,…,N)满足条件

因此 m = m_2m_3 …. *m_n时, 将出现上述理想情况。
为了加快实际游戏的进程,报数m应尽可能小, 所以m应为2, 3, …. , n的最小公倍数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值