圆圈报数-约瑟夫问题

问题概述

约瑟夫问题:n个人围成一圈,从第一个人开始报数,数到m的人出圈;再由下一个人开始报数,数到m的人出圈;…输出依次出圈的人的编号。n,m由键盘输入。

解题思路

1 初始级算法
循环报数,每次数到m的倍数就出局此时指向的人。
可以通过list或者是指针来实现保存当前还在游戏中的人的功能,通过索引去出局人。
但是这样的算法效率很低,基本上要O(m*n)的时间复杂度。

2优化算法
不需要通过循环报数,循环中很多步骤都是浪费的,如果当前游戏中还有n1个人,
如果给这个n1个人编号 从1到n1
那么下一次出局的人会是编号 (m+n1-1)%n1+1 或者简化为(m-1)%n1+1
通过这样的优化可以快速找到每一轮需要出局的人,这种情况下如list来存储当前还在游戏中的人的功能就会存在一定的优势。最后时间复杂度为O(n)

3动态规划算法
反思之前为什么要用额外的空间保存当前还在游戏中的人,那就是因为每一轮游戏结束之后部分人的索引改变了,如下
如果当前游戏还有n个人,从1到n进行编号,并且这一轮编号为a 的人出局了
原始: 1 2 3 … a-1 a a+1 …n
a出局之后: 1 2 3 … a-1 a+1 …n
编号a-1的人的下一位人的从n变为了n+1

而报数顺序变成了如下结构
a+1 a+2 …n 1 2 3 …a-1

如果我们按照这样的报数顺序对人重新编号,得到一个从1到n-1的数列,这个步骤相当于对原来人的编号进行循环左移a位的操作

这时候如果求得n-1个人按照m报数剩下人的编号,然后做一步逆向过程就能得到n个人按照m报数得到的最后一个人的编号。

可以归纳出这个动态过程中的必要条件。
初始状态f( 1, m) = 1 //如果只有一个人,最后肯定这能剩下这个人
递推条件f(n,m) = ( f(n-1,m) +(m+n-1)%n+1 +n-1)%n+1 //f(n-1,m) 循环右移 出局人编号位后得到f(n,m)的值
递推条件简化过程 f(n,m) = (f(n-1,m)+m+n-1+1+n-1)%n+1
f(n,m) = (f(n-1,m)+m-1)%m+1

这个算法的时间复杂度为O(n),空间复杂度为O(1), 应该是最接近最优算法的算法,可以通过循环来避免递归的内存占用

实现代码

    static int getWinner(int n, int m){
        if(n == 1){
            return 1;
        }
//      int peopleOut = (m+n-1)%n+1;
//      int peopleOut = (m-1)%n+1;
//      return (getWinner(n-1,m)+peopleOut+n-1)%n+1;
//      return (getWinner(n-1,m)+(m-1)%n+1+n-1)%n+1;
        return (getWinner(n-1,m)+m-1)%n+1;
    }

小计

这个问题的最终实现算法是动态规划算法的一个现实应用,如果问题具有递归的性质,那么使用动态规划能够获得较好的效果。我们在解决问题的时候需要保持抽取动态规划特征的意识。

参考

http://blog.csdn.net/wzdark/article/details/8444829

你可能感兴趣的:(数据结构与算法,算法)