什么是约瑟夫问题,约瑟夫问题是据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到留下一个人都自杀身亡为止。
解决这个问题,有多少种方法。
①最常用的方法是使用循环链表的解决。首先, 将这39 个犹太人构成一个循环链表,每个犹太人等同与一个结点,这个结点有后继结点相连,最后的结点与第一个结点相连。我实现的思路如下所示:
结点
翻译成源代码如下:
public class Node
{
private Node preNode;
private Node nextNode;
private int id;
private uint password;
public Node PreNode
{
get {
return preNode;
}
set {
preNode = value;
}
}
public Node NextNode {
get {
return nextNode;
}
set {
nextNode = value;
}
}
public int ID
{
get {
return id;
}
set {
id = value;
}
}
public uint Password
{
get {
return password;
}
set {
password = value;
}
}
}
循环链表
翻译成源代码如下:
private Node firstNode = null;
private Node lastNode=null;
private Node nextNode = null;
private int count = 0;
public int Count
{
get {
return count;
}
set {
count = value;
}
}
public Circle()
{
}
public void Add(int id,uint password)
{
count++;
Node node = new Node();
node.ID = id;
node.Password = password;
this.Add(node);
}
public void Add(int id)
{
count++;
Node node = new Node();
node.ID = id;
this.Add(node);
}
private void Add(Node node)
{
if (firstNode == null)
{
firstNode = node;
lastNode = firstNode;
lastNode.NextNode = firstNode;
lastNode.PreNode = firstNode;
firstNode.NextNode = lastNode;
firstNode.PreNode = lastNode;
}
else
{
lastNode.NextNode = node;
node.PreNode = lastNode;
node.NextNode = firstNode;
firstNode.PreNode = node;
lastNode = node;
}
}
public Node NextNode()
{
Node node=new Node();
if (nextNode == null)
{
node = firstNode;
nextNode = firstNode.NextNode;
}
else
{
node = nextNode;
nextNode = node.NextNode;
}
return node;
}
public void RemoveNode(Node node)
{
count--;
Node _preNode = node.PreNode;
Node _nextNode = node.NextNode;
_preNode.NextNode = _nextNode;
_nextNode.PreNode = _preNode;
}
把每个循环链表初始化,相应的源代码如下:
List<Node> outList = new List<Node>();
int index = 0;
int n = 7;
uint m = 20;
List<Node> nodeList = new List<Node>();
Node nd = new Node();
nd.ID = 1;
nd.Password = 3;
nodeList.Add(nd);
nd = new Circle.Node();
nd.ID = 2;
nd.Password = 1;
nodeList.Add(nd);
nd = new Circle.Node();
nd.ID = 3;
nd.Password = 7;
nodeList.Add(nd);
nd = new Circle.Node();
nd.ID = 4;
nd.Password = 2;
nodeList.Add(nd);
nd = new Circle.Node();
nd.ID = 5;
nd.Password = 4;
nodeList.Add(nd);
nd = new Circle.Node();
nd.ID = 6;
nd.Password = 8;
nodeList.Add(nd);
nd = new Circle.Node();
nd.ID = 7;
nd.Password = 4;
nodeList.Add(nd);
Circle c = new Circle();
foreach (Node node in nodeList)
{
c.Add(node.ID, node.Password);
}
这是把所有的数据添加到泛型数组中。
在进行点名,出列。相应的源代码如下:
while (c.Count > 0)
{
index++;
nd = c.NextNode();
if (index == m)
{
c.RemoveNode(nd);
outList.Add(nd);
index = 0;
m = nd.Password;
}
}
也是放入新的泛型数组中去。
把所显示的结果最终显示的源代码:
foreach (Circle.Node node in outList)
{
Console.WriteLine(node.ID);
}
最终运行的结果如下:
②基于位图算法的思想,
这里做法是建立一个数组来存储相应的数据,建立一个布尔变量来编辑是不是是不是进行了出列的标志,没出列一次,把相应的布尔的变量就置为假,一次循环操作,直到满足题意。这就基本思路,相应的流程图如下:
那实现的源代码就非常简单了,①对数据进行赋值的数组,一个布尔变量标记的数组。②判断布尔变量只有一个为真的方法。③依次报数,当报到相应的数字就进行了出列。④相应的索引大于数列的总长度又归零,直到布尔数组中一个为真的方法。源代码如下:
public static bool CheckArray(bool[] array)
{
int temp = 0;
for (int i = 0; i < array.Length; i++)
{
if (array[i])
{
temp++;
}
}
return temp == 1;
}
int[] array = new int[M];
;
bool[] blarray = new bool[M];
for (int i = 0; i < M; i++)
{
array[i] = (i + 1);
blarray[i] = true;
}
int count = 0;
int index = 0;
while (!CheckArray(blarray))
{
if (blarray[index])
{
count++;
}
if (count == N)
{
Console.WriteLine("数字" + array[index] + "出列");
count = 0;
blarray[index] = false;
}
index++;
if (index == M)
{
index = 0;
}
}
运行结果如下:
两种方法互有千秋。比较如下:
2
这就是我对约瑟夫环问题的一点点心得,请大家多多指教。
协后感,不要看这个算法,我以前在做一个callcenter的项目时候,也是完全依据这个算法解决的问题,在做一个操作系统调度的时候,也是这个思想.所以,他的实用价值蛮大,所以像腾讯,华为,度娘面试的时候也经常考这个试题或者基于这个试题的变种。