算法篇——约瑟夫环

问题背景

据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

问题研究

1.问题描述:n个人围成一圈(编号从1到n),从第1个人开始报数,报到m的人出列,从下一个人再重新报数,报到m的人出列,如此下去,直至所有人都出列。求最后一个出列的人的编号。例如:

N=6 M=5 开始:1 2 3 4 5 6     结果:5 4 6 2 3 1     :最后一个编号为1

2.数据结构
捋清楚待研究的问题之后,考虑数据结构。按照要求我们可以方便的利用一个数组去存储对应数据。每一个数组元素类型为:

enum status{
	Die,    //0
	Live    //1
};

typedef struct element{

	size_t index;
	enum status stu;

}element;

index标记每个人的编号,枚举变量stu标记每个人的状态(Die or Live)。所以我们直接可以用一个

private:
	vector<element> _arr;

来组织所有参与游戏的选手。

源码

Joseph.h

#ifndef _JOSEPH_H_
#define _JOSEPH_H_

#define MaxSize 100

#include <iostream>
using namespace std;
#include <vector>


enum status{
	Die,    //0
	Live    //1
};

typedef struct element{

	size_t index;
	enum status stu;

}element;



//应该是不需要先完整的实现循环队列的操作,目前分析并不涉及到队列的重新插入操作

class Joseph{

//typedef struct element element;


public:
	Joseph(int value);
	int calc(int index,int M);    //计算每一轮需要淘汰的index
	void clean(int index,int &step);   //清除需要淘汰项
	bool check(int num);     //检查是否退出
	void func(int M,int num);// 核心函数;M是间隔数字
	bool IsLegal(size_t Index); //判断元素合法性
	int add(int& Index);    //合法的+1
	void print();
private:
	vector<element> _arr;
	vector<int> _res;
	int N;
	

};      


#endif

Joseph.cpp


#include "joseph.h"



Joseph::Joseph(int value)
{

	_arr.resize(value);
    N=value;

	 for(int i=0;i<value;++i){
	 	_arr[i].index=i+1;
		_arr[i].stu=Live;

	}

}


bool Joseph::check(int num){

	int count=0;
	for(int i=0;i<_arr.size();++i){

		if(_arr[i].stu==Live){
			++count;
		} 


	}
	if(count>num)
		return false;
	if(count==num)
		return true;

}


bool Joseph::IsLegal(size_t Index){
	if(_arr[Index-1].stu==Live)
		return true;
	return false;

}





//这个add操作也应该考虑剩余元素的状态

int Joseph::add(int& Index){  //6 ---》1
	
	int N=_arr.size();
	Index=++Index;
	if(Index>N){
		Index%=N;
	}

	while(_arr[Index-1].stu==Die){
		++Index;
		if(Index>N){
			Index%=N;
		}
	}
	return Index;
}

void Joseph::clean(int index,int &step){

	_arr[index-1].stu=Die;
	_res.push_back(index);
	
	//step不能单纯的++,要先进行判断
   
	step=add(index);                              //每个位置的+1操作都需要用add()接口去替换
    while(_arr[step-1].stu==Die){
		int temp=add(step);
	}

}

void Joseph::print(){
	/*for(int i=0;i<_res.size();++i){
	cout<<_res[i]<<" ";
	} */

    //cout<<_res.back()<<endl;
    //N=6 M=5 开始:1 2 3 4 5 6     结果:5 4 6 2 3 1
    vector<int>v(100);
	for(int i=0;i<_res.size();++i){
		v[ _res[i] ]++;	
	}
	int j=0;
	for(j=1;j<=N;++j){
		if(v[j]==0){
			cout<<j<<" ";
			continue;
		}
		
	}

}

int Joseph::calc(int index,int M){  //6,5--->4     //确定下一个要杀死的人的位置

int count=1;
int tmp=0;

while(1){
	  if(_arr[index-1].stu==Live){
	     tmp=add(index);                                
         ++count;                
    }

	  if(IsLegal(tmp) && count==M )
			break;
 }

	return index;

}

void Joseph::func(int M,int num){   //num是最后保留的数字

	//游戏开始直到只剩下一个人存活结束
	int _count=N-num;
    int _step=1;    //下一轮报数的第一个人
	int _index=0; 
	int N=_arr.size();
    int tmp=M;
		//N=6 M=5 开始:1 2 3 4 5 6     结果:5 4 6 2 3 1
		//41 :3   tip:数组下标是从0开始,对应的index+1;
		
	//123456789     2 4 6 8 

	//Bug: 如果说M=1
	while(M<N && _count<=1){     //循环条件与num的限制也有关系
		  clean(M,_step);
		  //2468   369
		  M=M+tmp;  
		  ++_count;
	     	
	}


   while(!check(num)){
		
	    _index=calc(_step,tmp);  //预期:_index=4    _step=6 ,tmp=5 
		clean(_index,_step);     //_step=1

	}

}

Main.cpp


#include <iostream>
#include "joseph.h"
//using namespace std;



int main(){

	//测试用例不能全部达标,
	//优化,输入最后存活的x人编号,x=?;  改进一下check()函数

	Joseph jos(41);
    jos.func(3,2);
	jos.print();
	system("pause");
	return 0;
}


运行结果:
在这里插入图片描述代码调用解读:
1.第一行我们先创建一个Joseph类对象,传参一个int类型的值用于构造函数初始化。

	Joseph jos(41);    //41:对应有41个人参与游戏

2.第二行是算法的核心部分。调用func成员函数执行,第一个参数是间隔号,也就是报数的最大值。第二个参数是保留人数。

    jos.func(3,2);

意味着报数到 3 截至,游戏进行直到剩余 2 人存活。

3.第三行就是输出剩余存活人的编号

	jos.print();

Tip: 核心的算法也就是在func()函数中,代码并不长,大家可以参照源代码逐步理解。代码已经通过测试,如果有什么疑问或者发现什么bug也欢迎评论区补充指正。

发布了187 篇原创文章 · 获赞 208 · 访问量 22万+

猜你喜欢

转载自blog.csdn.net/tonglin12138/article/details/103955399