<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Josephus on chai2010 的博客</title>
    <link>https://chai2010.cn/tags/josephus/</link>
    <description>Recent content in Josephus on chai2010 的博客</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh-CN</language>
    <lastBuildDate>Sat, 05 Dec 2009 00:00:00 +0000</lastBuildDate>
    
        <atom:link href="https://chai2010.cn/tags/josephus/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Josephus问题</title>
      <link>https://chai2010.cn/post/2009/josephus-reseach/</link>
      <pubDate>Sat, 05 Dec 2009 00:00:00 +0000</pubDate>
      
      <guid>https://chai2010.cn/post/2009/josephus-reseach/</guid>
      
        <description>&lt;p&gt;&lt;strong&gt;1. 问题的由来&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Josephus问题是以10世纪的著名历史学家Flavius Josephus命名的. 据说, Josephus
如果没有数学才能, 他就不会在活着的时候出名! 在犹太人和古罗马人战争期间, 他是
陷如罗马人陷阱的41个犹太反抗者之一. 反抗者宁死不做俘虏, 他们决定围成一个圆圈,&lt;/p&gt;

&lt;p&gt;且围绕圆圈来进行, 杀死所有第3个剩下的人直到没有一个人留下. 但是, Josephus和一个&lt;/p&gt;

&lt;p&gt;不告发的同谋者感到自杀是愚蠢的行为, 所以以他快速计算出在此恶性循环中他和他的
朋友应该站的地方. 因此, 他们活了下来&amp;hellip;&lt;/p&gt;

&lt;p&gt;**2. 平凡的解法  **&lt;/p&gt;

&lt;p&gt;我们用一个循环连表来模拟他们的行为。为了省事，我直接找了一个一个java代码：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Josephus
{
    static class Node
    {
        int val; Node next;
        Node(int v) { val = v; }
    }
    public static void main(String[] args)
    {
        int N = Integer.parseInt(args[0]);
        int M = Integer.parseInt(args[1]);

        Node t = new Node(1);
        Node x = t;

        for(int i = 2; i &amp;lt;= N; x = (x.next=new Node(i++)));

        x.next = t;

        while(x != x.next)
        {
            for(int i = 1; i &amp;lt; M; i++) x = x.next;
            x.next = x.next.next;
        }
        Out.println(&amp;quot;Survivor is &amp;quot; + x.val);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;3. 递归公式&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;喜欢这个问题的朋友肯定不满足上面的方法，很想知道更简单的算法。
其实Josephus问题中的序列确实存在递归的公式。但是递归公式的推导
比较麻烦，我就直接给出结果。如果想了解详细过程可以查阅相关资料。&lt;/p&gt;

&lt;p&gt;假设有n个人，每次杀第m个人，则k为第k个被杀死的人&amp;hellip;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;j1: x  &amp;lt;- k*m
j2: if(x &amp;lt;= n) 输入结果x
j3: x &amp;lt;- floor((m*(x-n)-1)/(m-1)), goto j1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;以C语言实现如下：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;unsigned josephus(unsigned  m, unsigned  n, unsigned  k)
{
    unsigned x = km;
    while(x &amp;lt;= n) x = (m*(x-n)-1)/(m-1);
    return  x;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;4. m为2的情况&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;现在考虑一种m为2的特殊情形。
这时候有更简单的递归公式：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;x = 2*n + 1 - (2*n+1-2*k)*2^log2((2*n)/(2*n+1-2*k))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中，&lt;code&gt;log2((2*n)/(2*n+1-2*k))&lt;/code&gt;为计算&lt;code&gt;(2*n)/(2*n+1-2*k)&lt;/code&gt;以&lt;code&gt;2&lt;/code&gt;为底的对数，
结果向下取整数。&lt;/p&gt;

&lt;p&gt;观察&lt;code&gt;2^log2((2*n)/(2*n+1-2*k))&lt;/code&gt;整体，可以理解为将&lt;code&gt;(2*n)/(2*n+1-2*k)&lt;/code&gt;向下
舍取到2的幂。有些地方把这中运算称为地板函数，我们定义为flp2，下面是
C语言的实现：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;unsigned flp2(unsigned  x)
{
    unsigned y;
    do { y = x; x &amp;amp;= x-1; }while(x);
    return y;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中&lt;code&gt;x &amp;amp;= x-1;&lt;/code&gt;语句是每次把&lt;code&gt;x&lt;/code&gt;二进制最右边的&lt;code&gt;1&lt;/code&gt;修改为&lt;code&gt;0&lt;/code&gt;，直到最左边的1为止.
这种方法也可以用来计算x二进制中1的数目，当x二进制中1的数目比较小的
时候算法的效率很高。&lt;/p&gt;

&lt;p&gt;m为2的代码实现:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;unsigned josephus2k(unsigned n, unsigned k)
{
    unsiged t = (n&amp;lt;&amp;lt;1) - (k&amp;lt;&amp;lt;1) + 1;
    return (n&amp;lt;&amp;lt;1)+1 - t*flp2((n&amp;lt;&amp;lt;1)/t);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;5. m为2的情况, k为n的情形&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;该问题一般都是计算最后一个被杀的人的位置。
现在考虑更为特殊的，m为2的情况, k为n的情形。&lt;/p&gt;

&lt;p&gt;令k=n可以化简前边m=2的公式：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;x = 2*n + 1 - (2*n+1-2*n)*2^log2((2*n)/(2*n+1-2*n))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;即&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;x = 2*n + 1 - 2^log2(2*n)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;从二进制的角度可以理解为：
将n左移1位（即乘以2），然后将最右端设置为1（既加1），
最后将左端的1置为0（既减去&lt;code&gt;2*n&lt;/code&gt;的向下取的2的幂）。&lt;/p&gt;

&lt;p&gt;更简单的描述是将n的二进制表示循环左移动一位!&lt;/p&gt;

&lt;p&gt;例如: n为&lt;code&gt;1011001 -&amp;gt; 0110011 -&amp;gt; 110011&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;用代码实现为：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;unsigned josephus2n(unsigned n)
{
    return ((n-flp2(n))&amp;lt;&amp;lt;1)|1;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;继续修改：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;unsigned josephus2n(unsigned n)
{
    return ((n&amp;amp;(~flp2(n)))&amp;lt;&amp;lt;1)|1;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;参考资料: 具体数学.&lt;/p&gt;
</description>
      
    </item>
    
  </channel>
</rss>