Josephus问题及其推广 分析和算法优化

具体实现请见 http://blog.csdn.net/hnust_V/article/details/51747212
问题 C,D,I: Josephus问题
题目描述
n个人排成一圈,按顺时针方向依次编号1,2,3…n。从编号为1的人开始顺时针”一二”报数,报到m的人退出圈子。这样不断循环下去,圈子里的人将不断减少。最终一定会剩下一个人。试问最后剩下的人的编号。
输入
C,D:每组数据一行,每行一个正整数,代表人数n。 (1 <= n < k)
I:每组数据一行,每行两个正整数,为 n和m。 (1 <= n < k)
//C : k=1000,C,D m=2
//D,I: k=2^31
输出
每组输入数据输出一行, 仅包含一个整数,代表最后剩下的人的编号。

报告:
其中C题是属于数据结构应用范畴的,但是D,I题问题上界表明只能使用数学方法
先给出 C题的利用数据结构模拟法,因为是一个圈,所以考虑到用循环队列
请先实现 数据结构queue 的功能 或 使用C++ 中的STL

Cal(n, Que): //Que 为int型队列 
        for i=1 to n 
                Que.push(i);    //将编号1~n的人放入队列
        Count=0
        while Que.size() >1 //踢人直到剩余一个
                Count = Count +1 
                if Count%2=0 //报号为2
                    Que.pop(); //踢出
                else        //报号为1 则从队列中取出再放入对尾
                    a=Que.front()
                    Que.pop()
                    Que.push(a)
         return   Que.front()

可用于C,D的数学写法:
/以下内容引用了<<具体数学>> &1.4 约瑟夫问题 中部分数学知识/
m=2时 :
数学规律1:【是经过严格推导的】
有 f(2n+1)=2f(n)+1 ; f(2n)=2f(n)-1 ; f(1)=1
数学规律 2:【是由规律1所得】
将n表达成二进制 m ,m左循环一位便为答案
利用规律1: //若用C/C++ 请使用long long 实现

Cal(n):
    If n=1
         return 1
    if n%2=0    //偶数n
         return Cal(n/2)*2-1
    else
         return Cal((n-1)/2)*2+1

规律2同样易实现,此处不赘述
/以上内容引用了<<具体数学>> &1.4 约瑟夫问题 中部分数学知识/

但是对于I题 ( 约瑟夫问题的推广 ),解决本题,必须先对约瑟夫问题有根本上的理解
分析:
Josephus问题及其推广 分析和算法优化_第1张图片
首先,第一个出圈者编号必定为 m % n

于是变化为Josephus问题及其推广 分析和算法优化_第2张图片

令k = m % n+1,于是图等价为(a),再对k,k+1……k-2 重新标号1~n-1 便为图(b)
Josephus问题及其推广 分析和算法优化_第3张图片
我们可以发现前后的标号编号规则,计F(n)为n人组成的圈里编号


F(n)                         F(n-1)
K                               1
K+1                             2
K+2                             3
....                            ....
k-2                             n-1
k-1                             n               被剔除

很容易得到变化规律:
F(n) = [F(n-1)+k(n)-1] % n
k(n) = m % n + 1 //见上面
重新考虑 n-1人圈,很明显这是一个与n人圈的相同小规模子问题
由此递推下去 n-2,n-3,……,1 人圈是同类问题
而1人圈很明显 F(1)=1 //这货就是剩余者
我们又有序号变化规律(序号的状态转移方程),完全可以一步步推到这货在n人圈里的序号,问题便这样解决了
综上所述
k(n) = m % n + 1
F(n) = [F(n-1)+k(n)-1] % n
F(1)=1
再将k(n)带入式2有
F(n) = [F(n-1)+m % n] % n = [F(n-1)+m] % n
F(1)=1
这便是最终结论,也是I题求解的基础

给出结论两种实现
递归实现:

Cal(n,m):

    if n=1
        return 1
    else 
        ans = [Cal(n-1,m)+m]%n
    if ans=0
        ans = n  第0个应当为当前第n个
    return ans

递推实现:

Cal(n,m):
        ans = 1
        for i = 1 to n
            ans = (ans+m) %i
            if ans = 0
                ans = n 
        return ans

但是!即使O(n)的运算速度仍然不能AC该题,我们需要优化!
/以下算法由大牛们的博客提供了优化方向Orz,但不知具体是那位牛首先提出该优化策略,故此处不提供博客链接/
首先递归算法必然不能使用 //2^31必然会栈溢
考虑在递推上进行优化
Josephus问题及其推广 分析和算法优化_第4张图片

对于 ans = (ans+m) %i,如图可以发现当ans+m >= i时,ans即将被取余而不大于i,而当ans+m < i时 (ans+m) %i = ans+m
而 ans+m+m < i+1 仍然成立的话,甚至 ans+x*m < i+x-1都在成立,那么完全可以视为一个从 ans 开始增长的等差数列
令x为满足 ans+x*m < i+x-1的最大值
然后我们直接让ans变为ans+x*m,i直接增到i+x 从而跳过x-1步,得到优化,当然如果 i+x>n 那么ans+(n-(i-1))*m 就应当得解,此时正是第n人圈
那么对于n>m的情况下复杂度仅为O(m),
那么为什么是O(m)? 提示 :因为每次比m大时总会被对m取余

//给出伪代码请用long long实现
Cal(n,m):
    ans = 1,i=1
    if m = 1        //这里既是优化,也是完善不足->m=1时始终有ans+m>=i
         return n;//原理很简单 n个人报1退出,前n-1个都因报1被踢出
    while i<=n
            if ans+m < i //被优化的部分
                x = (i-1-ans)/(m-1) 
                if x=0      //这里防止x恰好是m-1的倍数
                    x = (i-1-ans)/(m-1)-1;
                if i+x > n 
                    ans = ans + (n+1-i)*m;
                    break
                else        
                     ans = ans + x*m
                     i = i + x
            else    //正常的递推
                ans = (ans+m)%i
                if ans = 0
                     ans = i
                     i=i+1
    return ans
最后让我们再次膜拜一下大牛们的神一般思路Orz

你可能感兴趣的:(经典算法问题分析和推广)