约瑟夫环是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数(从1开始报数),数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。

首先从编程的角度声明一下上面描述中的一点,就n,k,m这些都是下标从1开始的。而在实际编程中,我们一般将下标从0开始。所以这一点要注意一下。

第一种方法:使用队列。

这种解法直观,简单。首先你要明白队列这种数据结构,是一种先进先出的线性结构,如同生活中的排队,先到先处理。

明白了队列的结构。下面讨论一下如何应用队列解决约瑟夫环的问题。为了方便,我们假设从第1个人开始报数。从其定义中可知,每数到m的那个人退出,那么对于队列,我们首先从队头开始报数,

(1)如果不是m,把这个数从队头拿掉放到队尾;

(2)如果是m,那么让这个元素出队列;

如此循环下去,直接队列中的所有元素都出队列。

例子:

队列a,b,c 其中a为队头,c为队尾,报数为2时出队列

处理队头元素a,报数为1,不为2,这时取出队头元素a放到队尾,队列变为b,c,a 其中b为队头,a为队尾

处理队头元素b,报数为2,这时让b出队列,队列变为c,a 其中c为队头,a为队尾,报数从新开始,即下次c报数1,a报数2

...如此循环下去,直到所有元素出列,队列长度为0

下面再考虑如何从第k个元素开始,其实就是把第k个当作队头来使用就可以了。下面的c#代码先把数组元素放到一个队列中,然后在放的时候,先放第k个,依次放下去即可。这样第k个元素即队列的队头,也即从第k个元素开始报数。

c#代码:

        private static void JosephCircle(int[] numbers, int k, int m)
{
Queue<int> numbersQueue = new Queue<int>();
k = k - ; for (int i = k; i < numbers.Length; i++)
{
numbersQueue.Enqueue(numbers[i]);
} for (int i = ; i < k; i++)
{
numbersQueue.Enqueue(numbers[i]);
} int flag = ;
while (numbersQueue.Count > )
{
flag++; if (flag != m)
{
numbersQueue.Enqueue(numbersQueue.Dequeue());
}
else
{
Console.WriteLine(numbersQueue.Dequeue()); //输出出列项的编号,下标从1开始
flag = ;
}
}
}

上面的代码中,直接使用了.net 类库中的Queue类,当然了你也可以自己实现一个队列的数据结构,比如用链表,此处不作示例了。

第二种方法:递归

也可以采用递归的算法,这里我们只关注最后退出的那个人。

仍然假设从第一个人开始报数,每当数到m时退出:(下标从0开始)

如果只有一个人,那么最后退出的肯定就是这个人,下标为0。

如果有x个人,每当数到m时退出,且已知最后退出的人的下标为i。

那么当有x+1个人的时候,最后退出的人的下标应该是多少呢?下面我们来分析一下:

有x+1个人的时候,数到第m个(下标为m-1的人),这个人退出,注意此时只剩x个人了,然后从下一个人开始报数,这个人的下标为m%(x+1),这个人即为当有x个人的情况下,第一个开始报数的人。所以有:

x+1个人的时候的下标为m%(x+1)的人

就是

x个人的时候第一个报数的人

也就是说

x个人时候,下标为0的人,其实就是x+1个人的时候,下标为m%(x+1)的那个人

那么如果x个人的时候最后退出的人的下标为i,如果把这个人放回到x+1个的情况的时候,这个人的下标就应该是 (m % (x + 1) + i) % (x + 1) = (m + i) % (x + 1)

所以可以推导出一个公式,假设有x个元素时,最后退出的元素为f(x),那么有

f(x+1) = (m+f(x))%(x+1)

如果这里的推导没有看明白,可以参考一下文章最后的附录部分的推导,是从其他地方摘过来的。

所以有如下c#代码:

        private static int GetIndex(int itemCount, int m)
{
if (itemCount == )
{
return ;
}
else
{
return (GetIndex(itemCount - , m) + m) % itemCount;
}
}

现在再考虑从第k个人开始报数的情况,我们只需要在得到最后退出人的下标后稍微处理一下即可,然后根据下标输出相应的元素:

        private static void JosephCircleRecurcively(int[] numbers, int k, int m)
{
int winnerIndex = GetIndex(numbers.Length, m); //输出最后一个出列项的编号,下标从1开始
Console.WriteLine(numbers[(winnerIndex + k - ) % numbers.Length]);
}

注意,递归是比较占用内存的,所以如果数据量特虽巨大,这肯定是不合适的。下面一种直接求解的算法比较高效。

第三种方法:根据递归公式直接求解。

依然是只关注最后退出的那个人。

有了公式,那么可以一个for循环直接求解,代码如下:

        private static void JosephCircleMath(int[] numbers, int k, int m)
{
int winnerIndex = ; for (int i = ; i <= numbers.Length; i++)
{
winnerIndex = (winnerIndex + m) % i;
} //输出最后一个出列项的编号,下标从1开始
Console.WriteLine(numbers[(winnerIndex + k - ) % numbers.Length]);
}

当然了,这是最高效的算法。

最后是Main方法中的测试调用代码:

        static void Main(string[] args)
{
int[] numbers = { , , , , , };
int m = ;
int k = ;
JosephCircle(numbers, k, m);
JosephCircleMath(numbers, k, m);
JosephCircleRecurcively(numbers, k, m);
Console.ReadLine();
}

附录:

下面是百度百科上递归公式的推导过程,供参考:

无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂度高达O(nm),当n,m非常大(例如上百万,上千万)的时候,几乎是没有办法在短时间内出结果的。我们注意到原问题仅仅是要求出最后的胜利者的序号,而不是要读者模拟整个过程。因此如果要追求效率,就要打破常规,实施一点数学策略。

为了讨论方便,先把问题稍微改变一下,并不影响原意:

问题描述:n个人(编号0~(n-1)),从0开始报数,报到m-1的退出,剩下的人继续从0开始报数。求胜利者的编号。

我们知道第一个人(编号一定是(m-1)%n) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):

k k+1 k+2 ... n-2,n-1,0,1,2,... k-2

并且从k开始报0。

我们把他们的编号做一下转换:

k --> 0

k+1 --> 1

k+2 --> 2

...

...

k-3 --> n-3

k-2 --> n-2

序列1:0,1,2,3 … n-2,n-1

序列2:0,1,2,3 … k-2,k,…,n-2,n-1

序列3:k,k+1,k+2,k+3,…,n-2,n-1,0,1,2,3,…,k-2,

序列4:0,1,2,3 …,5,6,7,8,…,n-3,n-2

变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:

∵ k=m%n;

∴ x' = x+k = x+ m%n ; 而 x+ m%n 可能大于n

∴x'= (x+ m%n)%n = (x+m)%n

得到 x‘=(x+m)%n

如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况 ---- 这显然就是一个倒推问题!好了,思路出来了,下面写递推公式:

令f表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n].

递推公式:

f[1]=0;

f[i]=(f[i-1]+m)%i; (i>1)

小小c#算法题 - 12 - Joseph Circle(约瑟夫环)的更多相关文章

  1. 小小c#算法题 - 11 - 二叉树的构造及先序遍历、中序遍历、后序遍历

    在上一篇文章 小小c#算法题 - 10 - 求树的深度中,用到了树的数据结构,树型结构是一类重要的非线性数据结构,树是以分支关系定义的层次结构,是n(n>=0)个结点的有限集.但在那篇文章中,只 ...

  2. UVA 305 Joseph (约瑟夫环 打表)

     Joseph  The Joseph's problem is notoriously known. For those who are not familiar with the original ...

  3. poj 1012 &amp; hdu 1443 Joseph(约瑟夫环变形)

    题目链接: POJ  1012: id=1012">http://poj.org/problem?id=1012 HDU 1443: pid=1443">http:// ...

  4. hdu 1443 Joseph【约瑟夫环】

    题目 题意:一共有2k个人,分别为k个好人和k个坏人,现在我们需要每隔m个人把坏人挑出来,但是条件是最后一个坏人挑出来前不能有好人被挑出来..问最小的m是多少 约瑟夫环问题,通常解决这类问题时我们把编 ...

  5. 小小c#算法题 - 9 - 基数排序 (Radix Sort)

    基数排序和前几篇博客中写到的排序方法完全不同.前面几种排序方法主要是通过关键字间的比较和移动记录这两种操作来实现排序的,而实现基数排序不需要进行记录项间的比较.而是把关键字按一定规则分布在不同的区域, ...

  6. 【算法题12 解码方法decode way】

    1.来源LeetCode91 一条包含字母 A-Z 的消息通过以下方式进行了编码: 'A' -> 1 'B' -> 2 ... 'Z' -> 26 给定一个只包含数字的非空字符串,请 ...

  7. 小小c#算法题 - 10 - 求树的深度

    树型结构是一类重要的非线性数据结构,树是以分支关系定义的层次结构,是n(n>=0)个结点的有限集.关于树的基本概念不再作过多陈述,相信大家都有了解,如有遗忘,可翻书或去其他网页浏览以温习. 树中 ...

  8. 小小c#算法题 - 8 - 归并排序 (Merging Sort)

    “归并”的含义是将两个或两个以上的有序序列组合成一个新的有序序列.这个“归并”可以在O(n+m)的数量级上实现,但这同时也需要O(n+m)的空间复杂度.具体为:首先分配一个新的长度为n+m的空序列,然 ...

  9. 小小c#算法题 - 7 - 堆排序 (Heap Sort)

    在讨论堆排序之前,我们先来讨论一下另外一种排序算法——插入排序.插入排序的逻辑相当简单,先遍历一遍数组找到最小值,然后将这个最小值跟第一个元素交换.然后遍历第一个元素之后的n-1个元素,得到这n-1个 ...

随机推荐

  1. Redis底层探秘(三):字典

    字典,又称为符号表(symbol table).关联数组(associative array)或映射(map),是一种用于保存键值对的抽象数据结构. 字典经常作为一种数据结构内置在很多高级编程语言里面 ...

  2. 关于overflow:hidden

    (本文只针对hidden这个值的用处进行阐述) 关于overflow:hidden;很多人都知道他是溢出隐藏的一个属性,但是并不是很多人知道它的一些神奇的地方!首先先讲一下众所周知的溢出隐藏吧! 溢出 ...

  3. BZOJ4923 [Lydsy1706月赛]K小值查询

    题意 维护一个长度为n的正整数序列a_1,a_2,...,a_n,支持以下两种操作: 1 k,将序列a从小到大排序,输出a_k的值. 2 k,将所有严格大于k的数a_i减去k. \(n \leq 10 ...

  4. 【LeetCode】008. String to Integer (atoi)

    Implement atoi to convert a string to an integer. Hint: Carefully consider all possible input cases. ...

  5. 【Unity】publishing setting keystore作用

    http://blog.csdn.net/ldy597321444/article/details/53519753 android的默认调试签名,所有市场都不允许debug签名的应用发布的.所以,第 ...

  6. 学习动态性能表(9)--v$filestat

    学习动态性能表 第九篇--V$FILESTAT  2007.6.5 本视图记录各文件物理I/O信息.如果瓶颈与I/O相关,可用于分析发生的活动I/O事件.V$FILESTAT显示出数据库I/O的下列信 ...

  7. 自己写的工具:把Evernote(印象笔记)的笔记导入到博客(Blog)中

    Evernote是个强大的工具, 这个伴随了我快4年的工具让我积累好多笔记.但是,如何把evernote(印象笔记)中的笔记发布到博客中呢? 自己空闲时候用python 3写了个工具Evernote2 ...

  8. iOS系统架构和Object-C基本数据类型(1)

    iOS系统架构 基本数据类型 思维导图  下载 注:打开思维导图的软件:Mindjet MindManager 9

  9. SpringBoot JPA 中无法注入 JpaRepository 接口的问题及解决方案

    错误: 在Springboot  框架中使用JPA的过程中,怎么来实现数据库操作底层的交互呢?Spring JPA其实已经提供了一套很全面的解决方案,实现对数据库的增.删.查.改只需要继承JPA实现类 ...

  10. filter中获取spring bean

    import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import ja ...