zoukankan      html  css  js  c++  java
  • 约瑟夫问题-Josephus--及实例说明

    类似约瑟夫的问题又称为约瑟夫环。又称“丢手绢问题”。

      这个问题来自于这样的一个关于著名犹太历史学家 Josephus传说: 在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

      约瑟夫问题是个有名的问题,比如6个人围一个圈,从第一个人开始123123的报数,最后一个报完了循环到第一个继续报数,报到数字3的人就退出游戏。这样退出的顺序就是: 3    6    4    2    5,最后剩下1一个人。

    解决问题:

      就像题中描述的一样直接翻译成代码是(拯救约瑟夫的代码):

     1 #include<iostream>
     2 using namespace std;
     3 const int maxn = 1e6 + 10;
     4 main()
     5 {
     6     bool a[maxn] = {0};
     7     int n, m, i, f = 0, t = 0, s = 0;
     8     cin >> n >> m;
     9     do
    10     {
    11         ++t;//逐个枚举圈中的所有位置
    12         if(t > n)
    13             t = 1;//数组模拟环状,最后一个与第一个相连
    14         if(!a[t])
    15             s++;//第t个位置上有人则报数
    16         if(s == m)//当前报的数是m
    17         {
    18             s = 0;//计数器清零
    19             cout << t << " ";//输出被杀人编号
    20             a[t] = 1;//此处人已死,设置为空
    21             f++;//死亡人数+1
    22         }
    23     }
    24     while(f != n - m + 1);//直到死亡得剩下他们两个人为止
    25     cout << "
    死亡人数:" << f << " " << endl;
    26 //最后就剩下16和31没有被杀
    27 }
    View Code

      考虑到时间复杂度的问题,我们有下面一种改良的做法(约瑟夫递推算法):    

      为了讨论方便,先把问题稍微改变一下,并不影响原意:
     问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。
     题解:我们知道第一个人(编号一定是(m-1)) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k = m mod n的人开始报0继续计数)
    这样我们就可以写出递推公式:
      f[1]=0;
      f=(f+m) mod i; (i>1)
    代码展示:
     1 #include <iostream>
     2 #include <cstdio>
     3 using namespace std;
     4 //一圈一圈的循环的来回的转,最后剩一个人
     5 int main() {
     6     int n, m, f = 0;
     7     cin >> n >> m;
     8     for (int i = 1; i <= n; i++) f = (f + m) % i;
     9     cout << f + 1 << endl;
    10     //输出最后活着的一个人.
    11     return 0;
    12 }
    View Code

    猴子选王

      题意:一堆猴子都有编号,编号是1,2,3 ...m,这群猴子(m个)按照1-m的顺序围坐一圈,从第1开始数,每数到第N个,该猴子就要离开此圈,这样依次下来,直到圈中只剩下最后一只猴子,则该猴子为大王。

    解题思路<肯定比百度上面丰富>:

    ①、单向循环链表实现:

     1 #include <stdio.h>
     2 #include <malloc.h>
     3 #define LEN sizeof(struct monkey)//定义structmonkey这个类型的长度
     4 struct monkey
     5 {
     6     int num;
     7     struct monkey *next;
     8 };
     9 struct monkey *create(int m)
    10 {
    11     struct monkey *head, *p1, *p2;
    12     int i;
    13     p1 = p2 = (struct monkey*)malloc(LEN);
    14     head = p1;
    15     head->num = 1;
    16     for(i=1, p1->num = 1; i < m; i++)
    17     {
    18         p1 = (struct monkey*)malloc(LEN);
    19         p1->num = i + 1;
    20         p2->next = p1;
    21         p2 = p1;
    22     }
    23     p2->next = head;
    24     return head;
    25 }
    26 struct monkey *findout(struct monkey *start, int n)
    27 {
    28     int i;
    29     struct monkey *p;
    30     i = n;
    31     p = start;
    32     for(i = 1; i < n - 1; i++)
    33         p = p->next;
    34     return p;
    35 }
    36 struct monkey *letout(struct monkey *last)
    37 {
    38     struct monkey *out, *next;
    39     out = last->next;
    40     last->next = out->next;
    41     next = out->next;
    42     free(out);
    43     return next;
    44 }
    45 int main()
    46 {
    47     int m, n, i, king;
    48     struct monkey *p1, *p2;
    49     printf("请输入猴子的个数m:
    ");
    50     scanf("%d", &m);
    51     printf("每次数猴子的个数n:
    ");
    52     scanf("%d", &n);
    53     if(n == 1)
    54     {
    55         king=m;
    56     }
    57     else
    58     {
    59         p1 = p2 = create(m);
    60         for(i = 1;i < m; i++)
    61         {
    62             p2 = findout(p1, n);
    63             p1 = p2;
    64             p2 = letout(p1);
    65             p1 = p2;
    66         }
    67         king = p2->num;
    68         free(p2);
    69     }
    70     printf("猴王的编号是:%d
    ", king);
    71     return 0;
    72 }
    View Code

     单向链表的尾节点的下一个练到头节点上,这样实现循环,然后就判断删除即可。

    ②、数组模仿链表--有点并查集的思想:

     1 #include<stdio.h>
     2 #include<malloc.h>
     3 int main()
     4 {
     5     int *monkey, i, node, n, m;
     6     scanf("%d%d",&n, &m);
     7     monkey = (int*)malloc(sizeof(int)*(n+1));
     8     for(i=1;i<=n;i++)//初始化圈
     9     {
    10         monkey[i] = i + 1;//i表示编号为i的猴, monkey[i]的值表示编号为i的猴的下一个猴的编号
    11     }
    12     monkey[n] = 1;//编号为n的下一个猴的编号是1
    13     node = 1;
    14     printf ("The murdered monkey are:
    ");
    15     while(node != monkey[node])//当这个圈中只剩下一个猴的时候跳出循环.
    16     {
    17         for(i = 1; i < m - 1; i++)    //找出念m的那只猴
    18         {
    19             node = monkey[node];
    20         }
    21         printf("%d ", monkey[node]);//输出退出的猴编号
    22         monkey[node] = monkey[monkey[node]];//修改退出的前一个猴的monkey[node]为退出的后一个猴的编号
    23         node = monkey[node];//这句话中的node是退出的猴的后一个猴
    24     }
    25     printf("
    The king is:
    %d
    ", node);//输出最终大王的的编号
    26     return 0;
    27 }
    View Code

     创建数组的时候下标从1开始填数,1上面填2,2上面填3,...,最后n上面填1,这样达到循环的效果,然后以monkey[i] = i;为条件进行猴子的出队操作。

    ③、数组实现:

     1 //猴子选大王问题(约瑟夫环问题)
     2 #include <stdio.h>
     3 #include <string.h>
     4 #include <stdlib.h>
     5 /*
     6  *
     7  * 屏蔽的代码部分为测试使用.
     8  * 为使删除的过程更加明确.
     9  *
    10  */
    11 int fre (char mok[],int k)
    12 {
    13     int i;
    14 
    15     /*printf("
    猴子编号:
    ");
    16     for(i = 0; mok[i] != ''; i++)
    17         printf("%d	", mok[i]);//输出为踢出之前的编号,测试用
    18     */
    19     
    20     
    21     for(i = k; mok[i] != ''; i++)
    22     {
    23         mok[i] = mok[i+1];
    24     }//一个循环,将k以后的元素前移
    25 
    26 
    27     /*putchar('
    ');
    28     for(i = 0; mok[i] != ''; i++)
    29         printf("%d	", mok[i]);//输出踢出之后的编号,测试用
    30     printf("
    按回车继续下一轮:
    ");
    31     system ("pause");    //暂停,测试使用。
    32     */
    33 
    34     return 0;
    35 }
    36 int main()
    37 {
    38     char mok[50];
    39     int i;
    40     int n, s, b;//n表示猴子总数;s表示步进;b表示元素个数及大王编号
    41     int j, k;//j,k都是计数器
    42     mok[0] = 1;//初始化mok[0],让后面编号更简单的进行
    43     printf("请输入猴子的总数:
    ");
    44     scanf("%d", &n);//输入猴子的总数
    45     for(i = 1; i < n; i++)
    46     {
    47         mok[i] = i + 1;
    48     }//对猴子进行编号
    49     mok[n] = '';//用0来表示数组的结尾
    50     printf("请输入循环单位:
    ");
    51     scanf("%d", &s);//单位长度
    52     b = n;//统计猴子的个数
    53     for(j = 1, k = 0;;j++,k++)
    54     {
    55         if(b == 1)
    56         {
    57             b = mok[0];
    58             break;
    59         }//如果元素只剩下一个,那么退出循环
    60         if(j == s)
    61         {
    62             //printf("
    它出列了:%d
    ", mok[k]); //测试用
    63             fre(mok, k);//用于元素前移的函数
    64             b--;
    65             j = 1;
    66         }//将猴子从数组中踢出,并重置计数器J。
    67         
    68         //--------------------------------------当删除的是最后一个数字时另行考虑.
    69         
    70         if (mok[k] == '')
    71             j--;
    72 
    73         //---------------------------------------
    74 
    75         if(mok[k+1] == '')
    76             k=-1;//重置计数器k,因为后面有k++所以k要在重置基础上-1.
    77     }//判断是否为数组最后元素,重置计数器k。
    78     printf("
    最终大王是他:%d
    ", b);
    79     return 0;
    80 }
    View Code

     只是使用一个数组,然后单独定义下标和计数器解决问题。

    约瑟夫数学算法:

     1 #include <stdio.h>
     2 #include <conio.h>
     3 int main(void)
     4 {
     5     int n, i = 0, m, p;
     6     scanf("%d%d", &n, &m);//n总人数,m步长
     7     while(++i <= n)
     8     {
     9         p = i * m;
    10         while(p > n)
    11             p = p - n + (p - n - 1) / (m - 1);
    12         printf("%d ", p);
    13     }
    14     getch();
    15     return 0;
    16 }
    View Code

     寻找m和n之间的关系,初学者可以不掌握这种算法。

    ⑤、约瑟夫递推算法:

     1 #include <bits/stdc++.h>
     2 using namespace std;
     3 int king(int M,int N)
     4 {
     5     int k=0;
     6     for(int i = 2;i <= M; i++)
     7         k = (k + N) % i;
     8     return ++k;
     9 }
    10 int main()
    11 {
    12     int n, m;
    13     while (scanf("%d%d",&n, &m) && n && m)
    14     {
    15         cout << king(n, m) << endl;
    16     }
    17     return 0;
    18 }
    View Code

     寻找删除的时候的规律,利用递推关系写出递推式,很轻松的解决问题。

    ⑥、双向链表实现:

     1 #include <stdio.h>
     2 #include <malloc.h>
     3 #include <stdlib.h>
     4 struct node {
     5     int data;
     6     struct node *prior, *next;
     7 };
     8 struct node *creat (int n, struct node *h) {
     9     h = NULL;
    10     struct node *s, *q;
    11     for (int i = 1; i <= n; ++i) {
    12         s =  (struct node *)malloc(sizeof (struct node));
    13         s->data = i;
    14         s->next = NULL;
    15         if (h == NULL) {
    16             h = s;
    17             s->prior = h;
    18             s->next = h;
    19         } else {
    20             s->next = q->next;
    21             q->next = s;
    22             s->prior = q;
    23             h->prior = s;
    24         }
    25         q = s;
    26     }
    27     return h;
    28 }
    29 struct node *work (struct node *head, int m) {
    30     struct node *p, *q;
    31     p = head;
    32     while (p->next != p) {
    33         for (int i = 1;i < m; ++i) {
    34             q = p;
    35             p = p->next;
    36         }
    37         q->next = p->next;
    38         p->next->prior = q;
    39         printf ("%4d ", p->data);
    40         free (p);
    41         p = q->next;
    42     }
    43     return p;
    44 }
    45 int main () {
    46     int m, n;    //n代表一共有n只猴子, m表示循环次数.
    47     scanf ("%d%d", &n, &m);
    48     struct node *head;
    49     head = creat(n, head);
    50     printf ("被删除的节点是:
    ");
    51     printf ("
    新大王是:%4d
    ", work(head, m)->data);
    52     return 0;
    53 }
    View Code

     主要是练习双向链表的结构而已,原理和单向循环链表类似。

    其实还有队列也可以实现,不过利用队列实现和方法③的思想就相似了,有想法的初学者小码友可以自己去操作一下。

      先总结这些,再有灵感再往上面加.欢迎批评。

     

  • 相关阅读:
    【转载】WinCE编译一二三
    Implement the OEM Power Management on Windows CE
    ubuntu 精简系统
    simple awk tutorial
    debian 安装备份已安装软件包
    awk 简单教程
    Unix Sed Tutorial: Advanced Sed Substitution Examples
    dos修改子文件夹所有文件文件名
    Using Intel MKL with MATLAB Matlab使用多核CPU
    [转]学校的统一订书80%该烧掉——IT推荐书单
  • 原文地址:https://www.cnblogs.com/Ddlm2wxm/p/5808157.html
Copyright © 2011-2022 走看看