Chinaunix首页 | 论坛 | 博客
  • 博客访问: 244797
  • 博文数量: 62
  • 博客积分: 973
  • 博客等级: 准尉
  • 技术积分: 530
  • 用 户 组: 普通用户
  • 注册时间: 2011-11-16 23:25
文章分类

全部博文(62)

文章存档

2013年(1)

2012年(14)

2011年(47)

分类: C/C++

2011-12-19 15:37:36

1. 用数组
  1. /* n个人,从s开始间隔m,结果存到p指向的数组 */
  2. void josephus(int n,int s,int m,int *p)
  3. {
  4.     int i,j=0,sum,a[100];
  5.     for(i=0;i<n;i++)
  6.         a[i]=0;
  7.     for(i = s; i < n; i++,i%=n)
  8.     {
  9.         if(a[i] != 1)
  10.           j++;
  11.         if(j == m)
  12.         {
  13.             j = 0;
  14.             a[i] = 1;
  15.             sum += a[i];
  16.             if(i==0)
  17.                 *p++ = n;
  18.             else
  19.                 *p++ = i;
  20.             /* printf(" %d",i); */
  21.         }
  22.         if(sum==n)break;
  23.     }
  24. }
2. 单向循环链表
  1. //带头结点的循环链表解约瑟夫问题
  2. #include <stdio.h>
  3. #include <stdlib.h>

  4. typedef struct Node{
  5.     int num;
  6.     /* char card; */
  7.     struct Node *next;
  8. }JNode, *Link;

  9. // create circle link exclude head
  10. void init_link(JNode *head, int n)
  11. {
  12.     int i;
  13.     JNode *p;
  14.     for(i = 1, p = head; i <= n; ++i)
  15.     {
  16.         p->next = (JNode *)malloc( sizeof(JNode) );
  17.         p = p->next;
  18.         if(!p) /* perror(); */
  19.             exit(-1);
  20.         p->num = i;
  21.     }
  22.     p->next = head->next;
  23. }

  24. void josephus(int n, int k)
  25. {
  26.     int i;
  27.     JNode *head, *p;
  28.     head = (JNode *)malloc( sizeof(JNode) );
  29.     head->num = n;
  30.     head->next = head;
  31.     // initial link->num with 1,2...n
  32.     init_link(head, n);
  33.     p = head;

  34.     for(i = 1; p != p->next; ++i)
  35.     {
  36.         if(i == k)
  37.         {
  38.             i = 1;
  39.             printf("%d ", p->next->num);
  40.             //remove the next node;
  41.             JNode *removed = p->next;
  42.             p->next = removed->next;
  43.             free(removed);
  44.         }
  45.         p = p->next;
  46.     }
  47.     printf("left:%d\n", p->num);
  48.     printf("head->num=%d\n", head->num);
  49.     free(p);
  50.     free(head);
  51.     head = NULL;
  52. }

  53. int main()
  54. {
  55.     int n, k;
  56.     printf("Please input n,k :\n");
  57.     scanf("%d,%d", &n, &k);
  58.     josephus(n,k);

  59.     return 0;
  60. }
  1. //不带头结点,更简洁
  2. //n个人,从k开始,每次数到m者出列。
  3. void josephus(int n, int k, int m)
  4. {
  5.     int i;
  6.     LinkList list = NULL;
  7.     LinkList p;

  8.     /* create and initial a cycle link. */
  9.     list = (LinkList)malloc( sizeof(LNode) );
  10.     list->num = 1; /* head is the first element */
  11.     list->next = list;
  12.     p = list;
  13.     if(!p) exit(-1);
  14.     
  15.     for(i = 2; i <= n; ++i)
  16.     {
  17.         LNode *node = (LinkList)malloc( sizeof(LNode) );
  18.         if(!node) exit(-1);
  19.         node->num = i;
  20.         p->next = node;
  21.         p = node;
  22.     }
  23.     p->next = list;
  24.     /* skip the first k element. */
  25.     p = list;
  26.     for(i = 1; i < k; ++i)
  27.     {
  28.         p = p->next;
  29.     }
  30.     /* get the out sequence*/
  31.     LNode *pre = p;
  32.     while(p->next != p)
  33.     {
  34.         for(i = 1; i < m; ++i)
  35.         {
  36.             pre = p;
  37.             p = p->next; /* m will be here. */
  38.         }
  39.         pre->next = p->next;
  40.         printf("%4d ", p->num);
  41.         free(p);
  42.         p = pre->next;
  43.     }
  44.     printf("left:%4d\n", p->num);
  45.     free(p);
  46. }

  47. int main()
  48. {
  49.     int n, k;
  50.     printf("Please input n,k,m :\n");
  51.     scanf("%d,%d,%d", &n, &k, &m);
  52.     josephus(n,k,m);

  53.     return 0;
  54. }

3. 数学推导
为了讨论方便,先把问题稍微改变一下,并不影响原意:
问题描述:n个人编号0~(n-1),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。
网上流传甚广的所谓数学方法都写得不清不楚让人费解。
    我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):
    我们先看第一个人出列后的情况,显而易见,第一个出列的人的编号一定是m%n-1,这个人出列后,剩下的n-1个人组成了一个新的约瑟夫环,这个约瑟夫环的第一个人在最开始的环中的编号是k=m%n(就是第一个出列的人的下一个)
        k  k+1  k+2  ... n-2, n-1, 0, 1, 2, ... k-2并且从k开始报0。
    事实上,可以把这个环又映射成为一个新的环:
    n-2个   n-1个       n个
            0 k     -->   0
            1 k+1   -->   1
            2 k+2   -->   2
    x''--> x' k+3   -->   x   // x倒推 n-1次即可。x=f(n), f(1)=0
              ...    ....
              k-2   -->  n-2
                         n-1
    可以看出,这就是原问题中把n替换成n-1的情况,假设我们已经求出来在这种情况下最后胜利的那个人的顺序是x',
    那个倒推回去的那个人的编号就正好是我们要求的答案,显而易见,x应该是(x'+k)%n 。
注意,x'代表被映射的x在(n-1)个人中的顺序号。
    那么如何知道n-1个人下面的这个x呢,yes,就是n-2个人情况下得到的x''倒推回去,那么如何知道n-2情况下的x'呢,当然是求n-3个人,这就是一个递归的过程
    x=(x'+k)%n, x'=(x''+k)%(n-1), x是n个人的f(n),x'是f(n-1),依次倒推,得
        f(n)=(f(n-1)+m)%n。
    那么我们要求f(n),就从f(1)倒推回去即可
    f(n):表示n个人时,最后一个出列的人在这n个人中的顺序号(从0开始)。
    f(2)表示2个人时,最后一个出列的人在这2个人中的顺序号,是第0个还是第1个。
    最后f(1)若出列只能是第0个,但不是上述最后一个出列的人。
    f(1) = 0 , f(1)就是现在还剩下1个人,那么无论m为几,这个人总会出列,因此f(1)=0.
    f(n) = (f(n-1)+m)%n 

int josephus(int n, int m)
{
    int i, s;
    s = 0;  // f(1)=0;
    for(i = 2; i <= n; ++i)
    {
        s = (s + m) % i;
    }
    printf("the last is %d\n", s+1);
    return (s+1);
}

阅读(1291) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~