zoukankan      html  css  js  c++  java
  • 约瑟夫环问题

    题目:0,1,2,....n-1这个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。
    例如,0,1,2,3,4这5个数字组成一个圆圈,从数字0开始每次删除第三个数字,则删除的前四个数字依次是2,0,4,1因此最后剩下的数字是3.(淡黄色表示将要删除的结点,粉红色表示下一次开始的起点)
     
     
    这里我们可以通过std::list来模拟一个环形链表,由于std::list本身并不是一个环形结构,因此每当迭代器(Iterator)扫描到链表末尾的时候,我们要记得把迭代器移到链表的头部,这样就相当于按照顺序在一个圆圈里遍历了.这种思路的代码如下:
    int LastRemaining(unsigned int n,unsigned int m)
    {
     if(n<1||m<1)
     return -1;
    unsigned int i=0;
    list<int> numbers;
    for(i=0;i<n;++i)
    numbers.push_pack(i);
    list<int>::iterator current=numbers.begin();
    while(numbers.size()>1)
    {
     for(int i=0;i<m;++i)
    {
     current++;
     if(current==numbers.end())//找到"淡黄色"结点
     current=numbers.begin();
    }
    list<int>::iterator next = ++current;//next即为"粉红色"结点
    if(next==numbers.end())
    next=numbers.begin();
    --current;
    numbers.erase(current);//清除对应"淡黄色"结点
    current = next;//开始下一轮遍历
    }
    return *(current);//最后一个结点
    }
     
     
    创新的解法:
    首先我们定义一个关于n和m的方程f(n,m),表示每次在n个数字0,1.......,n-1中每次删除第m个数字最后剩下的数字。
    在这n个数字中,第一个被删除的数字是(m-1)%n.为了简单起见,我们把(m-1)%n记为k,那么删除K之后剩下的n-1个数字为0,1......,k-1,k+1,.......n-1,并且下一次删除从数字k+1开始计数。相当于在剩下的序列中,K+1排在最前面,从而形成k+1.....,n-1,0,1.....,k-1.该序列最后剩下的数字也应该是关于n和m的函数。由于这个序列的规律和前面最初的序列不一样(最初的序列是从0开始的连续序列),因此这个序列的规律和前面最初的序列不一样(最初的序列是从0开始的连续序列),因此该函数不同于前面的函数,记为f'(n-1,m).最初序列最后剩下的数字f(n,m)一定是删除一个数字之后的序列最后剩下的数字,即f(n,m)=f'(n-1,m).
    接下来我们把剩下的这n-1个数字的序列k+1,......,n-1,0,1,........,k-1做一个映射,映射的结果是形成一个从0到n-2的序列:
                     k+1 -> 0
                     k+2 -> 1
                     .....
                     n-1 -> n-k-2
                     0    -> n-k-1
                     1    -> n-k
                     .....
                     k-1 -> n-2
    我们把映射定义为p,则p(x)=(x-k-1)%n.它表示如果映射前的数字是x,那么映射后的数字是(x-k-1)%n.该映射的逆映射是p''(x)=(x+k+1)%n.
    由于映射之后的序列和最初的序列具有相同的形式,即都是从0开始的连续序列,因此依然可以用函数f来表示,记为f(n-1,m).根据我们的映射规则,映射之前的序列中最后剩下的数字f'(n-1,m).根据我们的映射规则,映射之前的序列中最后剩下的数字f'(n-1,m)=p''[f(n-1,m)]=[f(n-1,m)+k+1]%n,把k=(m-1)%n代入得到f(n,m)=f'(n-1,m)=[f(n-1,m)+m]%n.
    经过上面复杂的分析,我们终于找到了一个递归公式。要得到n个数字的序列中最后剩下的数字,只需要得到n-1个数字的序列中最后剩下的数字,并以此类推。当n=1时,也就是序列中开始只有一个数字0,那么很显然最后剩下的数字就是0.我们把这种关系表示为:
                  ->0    n=1
    f(n,m)=
                 ->[f(n-1,m)+m]%n  n>1
     
    下面是一段基于循环实现的代码:
    int LastRemaining(unsigned int n,unsigned int m)
    {
     if(n<1||m<1)
      return -1;
      int last=0;
      for(int i=2;i<=n;i++)
      last=(last+m)%i;
      return last;
    }
    可以看出,这种思路的分析过程尽管非常复杂,但写出的代码却非常简洁,这就是数学的魅力.最重要的是,这种算法的时间复杂度是O(n),空间复杂度是O(1),因此无论在时间效率还是空间效率都优于第一种方法.
     
  • 相关阅读:
    科学-化学:化学百科
    科学-物理:物理学 (自然科学学科)百科
    科学-建筑学-建筑美学:建筑美学百科
    科学-建筑学:建筑学百科
    科学-哲学-美学:美学(中国哲学二级学科)
    哲学:哲学(世界观学说、社会形态之一)
    科学-语文:语文(语言和文学的简称)
    科学-分析:分析
    建模:数学建模
    科学-数学:数学
  • 原文地址:https://www.cnblogs.com/wxdjss/p/5448393.html
Copyright © 2011-2022 走看看