MS的一道面试题~,求解

 是Josephus问题的变形


Josephus问题

1. 问题的由来

Josephus问题是以10世纪的著名历史学家Flavius Josephus命名的. 据说, Josephus
如果没有数学才能, 他就不会在活着的时候出名! 在犹太人和古罗马人战争期间, 他是
陷如罗马人陷阱的41个犹太反抗者之一. 反抗者宁死不做俘虏, 他们决定围成一个圆圈,
且围绕圆圈来进行, 杀死所有第3个剩下的人直到没有一个人留下. 但是, Josephus和一个
不告发的同谋者感到自杀是愚蠢的行为, 所以以他快速计算出在此恶性循环中他和他的
朋友应该站的地方. 因此, 他们活了下来...

2.  平凡的解法 
 
我们用一个循环连表来模拟他们的行为。为了省事,我直接找了一个一个java代码: 
 
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    <=  N;  x  =  (x.next=new  Node(i++)));   
                       x.next  =  t;   
 
                       while  (x  !=  x.next)   
                       {   
                                   for  (int  i  =  1;  i    <  M;  i++)  x  =  x.next;   
                                   x.next  =  x.next.next;   
                       }   
                       Out.println(  "Survivor  is    "  +  x.val);   
           }   
}
 
3.  递归公式 
 
喜欢这个问题的朋友肯定不满足上面的方法,很想知道更简单的算法。 
其实Josephus问题中的序列确实存在递归的公式。但是递归公式的推导 
比较麻烦,我就直接给出结果。如果想了解详细过程可以查阅相关资料。 
 
假设有n个人,每次杀第m个人,则k为第k个被杀死的人... 
 
j1:  x    <-  k*m 
j2:  if(x    <=  n)  输入结果x 
j3:  x    <-  floor((m*(x-n)-1)  /  (m-1)),  goto  j1 
 
以C语言实现如下: 
 
unsigned  josephus(unsigned  m,  unsigned  n,  unsigned  k) 

           unsigned  x  =  km; 
           while(x    <=  n)  x  =  (m*(x-n)-1)/(m-1); 
           return  x; 

4. m为2的情况 
 
现在考虑一种m为2的特殊情形。 
这时候有更简单的递归公式: 
 
x  =  2*n  +  1  -  (2*n+1-2*k)*2^log2((2*n)/(2*n+1-2*k)) 
 
其中,log2((2*n)/(2*n+1-2*k))为计算(2*n)/(2*n+1-2*k)以2为底的对数, 
结果向下取整数。 
 
联系2^log2((2*n)/(2*n+1-2*k))整体,可以理解为将(2*n)/(2*n+1-2*k)向下 
舍取到2的幂。有些地方把这中运算称为地板函数,我们定义为flp2,下面是 
C语言的实现: 
 
unsigned  flp2(unsigned  x) 

           unsigned  y; 
           do  {  y  =  x;  x  &=  x-1;  }while(x);   
           return  y;   

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

m为2的代码实现:

unsigned josephus2k(unsigned n, unsigned k)
{
unsiged t = (n<<1) - (k<<1) + 1;
return (n<<1)+1 - t*flp2((n<<1)/t);

5. m为2的情况, k为n的情形

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

令k=n可以化简前边m=2的公式:

x = 2*n + 1 - (2*n+1-2*n)*2^log2((2*n)/(2*n+1-2*n))
即,x = 2*n + 1 - 2^log2(2*n)

从二进制的角度可以理解为:
将n左移1位(即乘以2),然后将最右端设置为1(既加1),
最后将左端的1置为0(既减去2*n的向下取的2的幂)。

更简单的描述是将n的二进制表示循环右移动一位!
例如: n为1011001 -> 0110011 -> 110011

用代码实现为:

unsigned josephus2n(unsigned n)
{
return ((n-flp2(n))<<1)|1;
}

===================

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 <= N; x = (x.next=new Node(i++)));
x.next = t;

while (x != x.next)
{
for (int i = 1; i < M; i++) x = x.next;
x.next = x.next.next;
}
Out.println("Survivor is " + x.val);
}
}

unsigned josephus(unsigned m, unsigned n, unsigned k)
{
unsigned x = km;
while(x <= n) x = (m*(x-n)-1)/(m-1);
return x;
}

unsigned flp2(unsigned x)
{
unsigned y;
do { y = x; x &= x-1; }while(x);
return y;
}

unsigned josephus2n(unsigned n)
{
return ((n-flp2(n))<<1)|1;
}

unsigned josephus2k(unsigned n, unsigned k)
{
unsiged t = (n<<1) - (k<<1) + 1;
return (n<<1)+1 - t*flp2((n<<1)/t);
}


参考knuth相关著作

开始按照我的想法第n次拿走的肯定是关于pow(2,n)的奇数倍,所以就是把数2进制表示,前10为不能够为1 所以答案应该是1024,但是后来看了关于这个问题的算法才知道,还与数的个数为奇数或者偶数有关,按照上面的理论口算也知道是1952

下面是我的程序实现,循环链表的运用

#include"stdio.h"
#include"stdlib.h"
#include"time.h"
#include"math.h"

typedef struct node //define the node of the linklist
{
 int number ;
 struct node * next ;
}lNode ,*pNode ;

pNode head ;
pNode tail ;

void creatLinkList ( int n ) // creat the linklist
{
 head = ( pNode ) malloc ( sizeof ( lNode));
 head->number = 1 ;
 head->next = NULL ;
 tail = head ;
 for ( int i = 2 ; i <= n ; i ++ )
 {
  pNode newnode ;
  newnode = ( pNode ) malloc ( sizeof(lNode) );
  newnode->number = i ;
  newnode->next = tail->next ;
  tail->next = newnode ;
  tail = newnode ;
 }
 tail->next = head ; //circle linklist
}

void freeMem(pNode p) // free the momeroy the  node takes
{
 free (p);
 p = NULL ;
}

void deleteNode()  // delete the node after pfNode
{
      pNode p = tail->next ;
   tail->next = p->next ;
   head = tail ->next ;
   freeMem(p);
}


int main()
{
    int n ;
 int count = 0 ;
 printf("please input the number of node :");
 scanf("%d",&n);
 creatLinkList(n);
 while ( head->next != head)
 {
  count ++ ;
  if ( count % 2 == 1)
   {
    deleteNode();
       continue ;
  }
  tail = head ;
  head = head->next ;
 }
 printf("the last node is %d /n",head->number);
 return 0 ;
}
运行结果:please input the number of node :2000
the last node is 1952

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值