代码改变世界

循环链表范例Josephus问题

2012-07-27 23:12  youxin  阅读(592)  评论(0编辑  收藏  举报

  一群小孩围成一圈,任意假定一个数N,从第一个小孩起,逆时针方向数,每数到第M个小孩时,该小孩就离开,然后再由下一个重新报数,小孩不断离开,圈子不断缩小。最后,剩下的一个小孩便是胜利者。究竟胜利者是第几个小孩呢?

   类似这样的问题都叫Josephus(约瑟夫斯)问题。我们可以用一个循环链表表示排成圆圈的人,整数i代表第i个人,先为1号建立一个单节点循环链表,再把2号到N号依次插入到1号节点后面,这样就形成了一个由1--N(逆时针)的员,最后x指向N,然后我们从1号开始跳过M-1个节点,把第M-1个节点的链接改为指向第M+1号节点,一直这样做,直到只剩下一个节点为止。

#include<iostream>
using namespace std;
typedef int T;
struct node
{
    T data;
    struct node *next;
    node(T x,node *t):data(x),next(t) { }
};
typedef struct node *Link;


int main()
{
    int i, N,M;
    cin>>N>>M;
    Link t=new node(1,0); 
    t->next=t;

    Link x=t;
    for(i=2;i<=N;i++)
        x=(x->next=new node(i,t));
    while(x!=x->next)
    {
        for(i=1;i<M;i++)  x=x->next;
        x->next=x->next->next;
    }
    cout<<x->data<<endl;
}

 

 递推思路:

本思路可用O(n)的复杂度解决最原始的Josephus问题,即只找出最后一个幸免者。关于约瑟夫(Josephus)问题:

N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。例如N=6,M=5,被杀掉的人的序号为5,4,6,2,3。最后剩下1号。

为了方便使用mod处理数据,将N个人编号0-N-1,每次报数从0开始,报到M-1的被杀掉。

第一个被杀掉的一定是编号(M-1)mod(N)的人,他被杀掉后,令k=(M)mod(N),则剩下的问题为:编号为:k,k+1,k+2,…,n-1,0,1,…,k-2的N-1个人,从k开始从0进行报数,报到M-1的人被杀掉。所以如果做一个i’ = (i-k)mod(N)的映射,就可以真正将问题转换成J(N-1,M),解出答案后再做逆映射i = (i’+k)mod(N)即可得到J(N,M)的解,即J(N,M) = (J(N-1,M)+k)mod(N)。又知道k = (M)mod(N),所以递推公式的形式是J(N,M) = (J(N-1,M)+M)mod(N)。递推起点是J(1,M)=0。(注意k,k+1,k+2,…n-1,0,1,…k-2的序列在考虑mod操作后并没有数据上的跳跃,因此做了映射后才仍然是问题的原型,抹去了杀掉一个人的影响。)

J(1,M) = 0

J(N,M) = (J(N-1,M)+M)mod(N)

这样,只要从1推到N就可以得出问题的答案,复杂度O(N)。

深入:

http://blog.pfan.cn/xiangyu/3527.html

 

 

用数学方法解的时候需要注意应当从0开始编号,因为取余会等到0解。

实质是一个递推,n个人中最终存活下来的序号与n-1个人中存活的人的序号有一个递推关系式。

 

分析:

假设除去第k个人。

0, 1, 2, 3, ..., k-2, k-1, k, ..., n-1  //original sequence (1)

0, 1, 2, 3, ..., k-2,      , k, ..., n-1  //get rid of kth person (2)

k, k+1, ..., n-1,    0,    1,        ..., k-2  //rearrange the sequence (3)

0, 1,     ..., n-k-1, n-k, n-k+1, ..., n-2  //the n-1 person (4)

我们假设f(n)的值为n个人中最后存活的人的序号,则

注意到(2)式(3)式(4)式其实是同一个序列。

注意(1)式和(4)式,是同一个问题,不同的仅仅是人数。

假设我们已知f(n-1),即(4)式中最后剩下的人的序号,则(3)式所对应的序号,就是f(n),即(1)式n个人中最后存活的序号。

而从(3)(4)式中我们不难发现有这样一个递推式:

f(n) = (f(n-1) + k) % n

显然,f(1) = 0。

于是递推得f(n)

#include <stdio.h>

int main()
{
    int n, k, m, i, x;
    while (scanf("%d%d%d", &n, &k, &m) != EOF) {
    if (n==0 && k==0 && m==0) break;
    x = 0;
    for (i=2; i!=n; ++i)
        x = (x + k) % i;
    x = (x + m) % i + 1;
    printf("%d\n", x);
    }
    return 0;
}