约瑟夫问题有很多种解法及其变种,这里的约瑟夫环问题是这样的:
约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列.
大多数的约瑟夫环问题解法是利用面向过程的方法编写的(其实我没有看过多少。这里我给出一个利用C++面向对象的编程思想编写的代码,用于解决上面的约瑟夫环问题。这里的核心算法也是动态链表。
首先阐述一下我的算法思想:
要将这个结点踢出局外,也就是将它上一个结点的指向下一个结点的指针指向要出局的结点的下一结点,同时为了保持这个环,还要设置要出局的结点的下一结点的指向上一结点的指针指向要出局的结点的上一结点。如图:
最后不要忘了删除那个不要的结点。对分配在堆上的对象,C++是要手动进行内存管理的。
上面是主要的思想,下面是算法的实现
ReNode StartAgain(_Josephus*)是一个主要的函数,大家应该也注意到了前面的返回类型ReNode到底是什么呢?下面我来和大家说说吧。
ReNode的定义如下:
ReNode StartAgain(_Josephus*)的函数定义如下:
最后我们来看看最后一个函数void Josephus::Action();它的作用是将每一轮要出局的人的编号保存在类的jose中,jose是一个[code=c]vectot<int>[/code],用于保存整个游戏的结果,并且开始新一轮的游戏,直到所有人都出局。
函数void Josephus::Action();的定义如下:
下面给出完整的实现代码:
在文件中josephus.h
约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列.
大多数的约瑟夫环问题解法是利用面向过程的方法编写的(其实我没有看过多少。这里我给出一个利用C++面向对象的编程思想编写的代码,用于解决上面的约瑟夫环问题。这里的核心算法也是动态链表。
首先阐述一下我的算法思想:
每个参加游戏的人看做是一个链表中的一个结点,结点的内容为:
想像一下n个人围坐成一圈,手拉着手。这里的链表实现也是一样的,将n个结点首尾相连,形成一个环。像这样:
现在假设某一结点是表头,如:
要将这个结点踢出局外,也就是将它上一个结点的指向下一个结点的指针指向要出局的结点的下一结点,同时为了保持这个环,还要设置要出局的结点的下一结点的指向上一结点的指针指向要出局的结点的上一结点。如图:
最后不要忘了删除那个不要的结点。对分配在堆上的对象,C++是要手动进行内存管理的。
上面是主要的思想,下面是算法的实现
首先定义结点:
struct _Josephus
{
int data;//当前的数据点
_Josephus* previous;//上一个结点
_Josephus* next;//下一个结点
};
然后定义操作的类
//操作类
class Josephus
{
public:
Josephus() = delete;
Josephus(int count, int number) :Count(count), Number(number){}
~Josephus();
void CreateLink();//创建整个链表,并保存表头在head里。整个链表将构成一个环
void Action();//利用创建好的链表,并返回结果
vector<int> jose;//保存的结果
private:
int Count;//参加游戏的人数
int Number;//关键数字m
_Josephus* head;//表头
ReNode StartAgain(_Josephus*);//传入(新的)开始的结点,返回ReNode并且完成搭桥过程
};
void CreateLink();将用于创建这个链表,并将表头保存在Josephus类的数据成员head中。 函数定义如下:
void Josephus::CreateLink()
{
_Josephus* head=new _Josephus;//表头
_Josephus* next;//下一个结点
_Josephus* curr;//当前结点
head->data = 1;
curr = head;
for (int i = 1; i < this->Count; i++)
{
next = new _Josephus;
curr->next = next;
next->data = i + 1;
next->previous = curr;
curr = next;
}
//将这个链表构成一个环
next->next = head;
head->previous = next;
this->head = head;
}
这个函数应该不难,大家先这样看看吧。
ReNode StartAgain(_Josephus*)是一个主要的函数,大家应该也注意到了前面的返回类型ReNode到底是什么呢?下面我来和大家说说吧。
ReNode的定义如下:
//下面是辅助类
struct ReNode
{
int result;//当前轮的结果
_Josephus* curr;//下一轮开始的结点位置
};
成员result保存的是要出局的那个人的编号,而curr是下一轮要开始的结点。有了这个辅助结构,算法会简单很多。
ReNode StartAgain(_Josephus*)的函数定义如下:
ReNode Josephus::StartAgain(_Josephus* head)
{
ReNode node;
int num=1;//用于循环计数,从1开始
if (head->next == head)//如果当前结点的下一结点是其本身,则游戏结束——因为只有一个结点了
{
//设置ReNode
node.curr = NULL;//这里设置为空是为了退出Action的while循环
node.result = head->data;
}
else
{
/*
过程:
1.进行计数
2.得到ReNode
3.设置当前轮游戏结果的上一结点的下一结点为当前轮游戏结果的下一结点和
当前结点的下一结点的上一结点为当前结点的上一结点——相当于将这个结点踢出局外,游戏人数将减少一个
*/
//步骤1.进行计数
while (num!=this->Number)
{
head = head->next;
num++;
}//退出while时,head刚好是数到this->Number的那个结点
//步骤2.得到ReNode
//设置ReNode
node.curr = head->next;
node.result = head->data;
//步骤3.
head->previous->next = head->next;
head->next->previous = head->previous;
进行内存管理——释放游离的结点的内存
delete head;
}
return node;
}
最后我们来看看最后一个函数void Josephus::Action();它的作用是将每一轮要出局的人的编号保存在类的jose中,jose是一个[code=c]vectot<int>[/code],用于保存整个游戏的结果,并且开始新一轮的游戏,直到所有人都出局。
函数void Josephus::Action();的定义如下:
void Josephus::Action()
{
ReNode node;
while (this->head!=NULL)
{
node = StartAgain(this->head);//从head处开始游戏,并返回ReNode
this->jose.push_back(node.result);//得到每一轮的游戏结果
this->head = node.curr;//下一轮开始的结点
}
}
到此,整个算法的思想和主要的代码都列出来了。
下面给出完整的实现代码:
在文件中josephus.h
#pragma once
#ifndef __JOSEHPUS__HH
#define __JOSEHPUS__HH
#include<vector>
using std::vector;
//真正的结点
struct _Josephus
{
int data;//当前的数据点
_Josephus* previous;//上一个结点
_Josephus* next;//下一个结点
};
//下面是辅助类
struct ReNode
{
int result;//当前轮的结果
_Josephus* curr;//下一轮开始的结点位置
};
//操作类
class Josephus
{
public:
Josephus() = delete;
Josephus(int count, int number) :Count(count), Number(number){}
~Josephus();
void CreateLink();//创建整个链表,并保存表头在head里。整个链表将构成一个环
void Action();//利用创建好的链表,并返回结果
vector<int> jose;//保存的结果
private:
int Count;
int Number;
_Josephus* head;//表头
ReNode StartAgain(_Josephus*);//传入(新的)开始的结点,返回ReNode并且完成搭桥过程
};
#endif
在文件josephus.cpp中
#include "Josephus.h"
Josephus::~Josephus()
{
delete head;
}
void Josephus::CreateLink()
{
_Josephus* head=new _Josephus;//表头
_Josephus* next;//下一个结点
_Josephus* curr;//当前结点
head->data = 1;
curr = head;
for (int i = 1; i < this->Count; i++)
{
next = new _Josephus;
curr->next = next;
next->data = i + 1;
next->previous = curr;
curr = next;
}
//将这个链表构成一个环
next->next = head;
head->previous = next;
this->head = head;
}
void Josephus::Action()
{
ReNode node;
while (this->head!=NULL)
{
node = StartAgain(this->head);//从head处开始游戏,并返回ReNode
this->jose.push_back(node.result);//得到每一轮的游戏结果
this->head = node.curr;//下一轮开始的结点
}
}
ReNode Josephus::StartAgain(_Josephus* head)
{
ReNode node;
int num=1;//用于循环计数,从1开始
if (head->next == head)//如果当前结点的下一结点是其本身,则游戏结束——因为只有一个结点了
{
//设置ReNode
node.curr = NULL;//这里设置为空是为了退出Action的while循环
node.result = head->data;
}
else
{
/*
过程:
1.进行计数
2.得到ReNode
3.设置当前轮游戏结果的上一结点的下一结点为当前轮游戏结果的下一结点和
当前结点的下一结点的上一结点为当前结点的上一结点——相当于将这个结点踢出局外,游戏人数将减少一个
*/
//步骤1.进行计数
while (num!=this->Number)
{
head = head->next;
num++;
}//退出while时,head刚好是数到this->Number的那个结点
//步骤2.得到ReNode
//设置ReNode
node.curr = head->next;
node.result = head->data;
//步骤3.
head->previous->next = head->next;
head->next->previous = head->previous;
进行内存管理——释放游离的结点的内存
delete head;
}
return node;
}
最后还有一个用于辅助的头文件,内容如下:
在NHelper.h中
#include<iostream>
#ifndef __NAMESPACEHELPER__HH__
#define __NAMESPACEHELPER__HH__
#define NH__HH using std::cin;using std::cout;using std::endl;
#endif