03-数据结构与算法(约瑟夫问题栈笔记)
约瑟夫问题栈
-
问题描述
15个教徒与15个非教徒在深海遇险,必须将一半的人投入大海,其余的人才能幸免于难,于是想到一个方法,30个人围成一圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环直到余15个人为止,问怎么样排法,才能使每次投入大海的都是非教徒?
-
循环链表实现
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication5
{
class CirculLinkedList
{
//元素个数
private int count;
//尾指针
private Node tail;
//当前节点的前节点
private Node CurrPrev;
//增加元素
public void Add(object value)
{
Node newNode = new Node(value);
if (tail==null)
{//链表为空
tail = newNode;
tail.next = newNode;
CurrPrev = newNode;
}
else
{//链表不为空,把元素增加到链表结尾
newNode.next = tail.next;
tail.next = newNode;
if (CurrPrev==tail)
{
CurrPrev = newNode;
}
tail = newNode;
}
count++;
}
//删除当前元素
public void RemoveCurrNode()
{
//为null代表为空表
if (tail == null)
{
throw new NullReferenceException("集合中没有任何元素!");
}
else if (count==1)
{
tail = null;
CurrPrev = null;
}
else
{
if (CurrPrev.next==tail)
{
tail = CurrPrev;
}
CurrPrev.next = CurrPrev.next.next;
}
count--;
}
//当前节点移动步数
public void Move(int step)
{
if (step<0)
{
throw new ArgumentOutOfRangeException("step", "移动步数不可为负数!");
}
if (tail==null)
{
throw new NullReferenceException("链表为空!");
}
for (int i = 0; i < step; i++)
{
CurrPrev = CurrPrev.next;
}
}
//获得当前节点
public object Current
{
get {return CurrPrev.next.item ;}
}
public override string ToString()
{
if (tail==null)
{
return string.Empty;
}
string s = "";
Node temp = tail.next;
for (int i = 0; i < count; i++)
{
s += temp.ToString() + " ";
temp = temp.next;
}
return s;
}
public int Count
{
get {return count ;}
}
private class Node
{
public Node(object value)
{
item = value;
}
//放置数据
public object item;
public CirculLinkedList.Node next;
public override string ToString()
{
return item.ToString();
}
}
}
}
-
构造循环链表,解决约瑟夫问题
static void Main(string[] args)
{
CirculLinkedList lst = new CirculLinkedList();
string s = string.Empty;
Console.Write("请输入总数:");
int count = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("请输入每次要报到几!");
int m = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("报数开始~");
for (int i = 1; i <= count; i++)
{//构建循环列表
lst.Add(i);
}
Console.WriteLine(lst.ToString());
while (lst.Count>15)
{
lst.Move(m);
s += lst.Current.ToString() + " ";
lst.RemoveCurrNode();//把报数的人扔入大海
// Console.WriteLine("剩余的人为: "+lst.ToString());
Console.WriteLine(lst.Current + "开始报数!");
}
Console.WriteLine("被扔入大海的人为:"+s+lst.Current);
Console.ReadLine();
}
2.笔算解决
笔算解决约瑟夫问题
在M比较小的时候 ,可以用笔算的方法求解,
M=2
即N个人围成一圈,1,2,1,2的报数,报到2就去死,直到只剩下一个人为止。
当N=2^k的时候,第一个报数的人就是最后一个死的,
对于任意的自然数N 都可以表示为N=2^k+t,其中t<n/2
于是当有t个人去死的时候,就只剩下2^k个人 ,这2^k个人中第一个报数的就是最后去死的。这2^k个人中第一个报数的人就是2t+1
于是就求出了当M=2时约瑟夫问题的解:
求出不大于N的最大的2的整数次幂,记为2^k,最后一个去死的人是2(N-2^k)+1
M=3
即N个人围成一圈,1,2,3,1,2,3的报数,报到3就去死,直到只剩下一个人为止。
此时要比M=2时要复杂的多
我们以N=2009为例计算
N=2009,M=3时最后被杀死的人记为F(2009,3),或者可以简单的记为F(2009)
假设现在还剩下n个人,则下一轮将杀死[n/3]个人,[]表示取整,还剩下n-[n/3]个人
设这n个人为a1,a2,...,a(n-1),an
从a1开始报数,一圈之后,剩下的人为a1,a2,a4,a5,...a(n-n mod 3-1),a(n-n mod 3+1),..,an
于是可得:
1、这一轮中最后一个死的是a(n-n mod 3),下一轮第一个报数的是a(n-n mod 3+1)
2、若3|n,则最后死的人为新一轮的第F(n-[n/3])个人
若n mod 3≠0 且f(n-[n/3])<=n mod 3则最后死的人为新一轮的第n-[n/3]+F(n-[n/3])-(n mod 3)人
若n mod 3≠0 且f(n-[n/3])>n mod 3则最后死的人为新一轮的第F(n-[n/3])-(n mod 3)人
3、新一轮第k个人对应原来的第 3*[(k-1)/2]+(k-1)mod 2+1个人
综合1,2,3可得:
F(1)=1,F(2)=2,F(3)=2,F(4)=1,F(5)=4,F(6)=1,
当f(n-[n/3])<=n mod 3时 k=n-[n/3]+F(n-[n/3])-(n mod 3),F(n)=3*[(k-1)/2]+(k-1)mod 2+1
当f(n-[n/3])>n mod 3时 k=F(n-[n/3])-(n mod 3) ,F(n)=3*[(k-1)/2]+(k-1)mod 2+1
这种算法需要计算 [log(3/2)2009]次 这个数不大于22,可以用笔算了
于是:
第一圈,将杀死669个人,这一圈最后一个被杀死的人是2007,还剩下1340个人,
第二圈,杀死446人,还剩下894人
第三圈,杀死298人,还剩下596人
第四圈,杀死198人,还剩下398人
第五圈,杀死132人,还剩下266人
第六圈,杀死88人,还剩下178人
第七圈,杀死59人,还剩下119人
第八圈,杀死39人,还剩下80人
第九圈,杀死26人,还剩下54人
第十圈,杀死18人,还剩36人
十一圈,杀死12人,还剩24人
十二圈,杀死8人,还剩16人
十三圈,杀死5人,还剩11人
十四圈,杀死3人,还剩8人
十五圈,杀死2人,还剩6人
F(1)=1,F(2)=2,F(3)=2,F(4)=1,F(5)=4,F(6)=1,
然后逆推回去
F(8)=7 F(11)=7 F(16)=8 f(24)=11 f(36)=16 f(54)=23 f(80)=31 f(119)=43 f(178)=62 f(266)=89 f(398)=130
F(596)=191 F(894)=286 F(1340)=425 F(2009)=634

浙公网安备 33010602011771号