算法:约瑟夫问题:01 来源:百度百科

2021-02-04 01:16

阅读:723

算法:约瑟夫问题:01 来源:百度百科

时间:2020-04-29 00:38:09      阅读:76      评论:0      收藏:0      [点我收藏+]

标签:程序   res   last   lse   order   pac   procedure   要求   因此   

https://baike.baidu.com/item/约瑟夫问题/3857719?fr=aladdin

约瑟夫问题问题来历

据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。 [1] 
17世纪的法国数学家加斯帕在《数目的游戏问题》中讲了这样一个故事:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行直到仅余15个人为止。问怎样排法,才能使每次投入大海的都是非教徒。
问题分析与算法设计
约瑟夫问题并不难,但求解的方法很多;题目的变化形式也很多。这里给出一种实现方法。
题目中30个人围成一圈,因而启发我们用一个循环的链来表示,可以使用结构数组来构成一个循环链。结构中有两个成员,其一为指向下一个人的指针,以构成环形的链;其二为该人是否被扔下海的标记,为1表示还在船上。从第一个人开始对还未扔下海的人进行计数,每数到9时,将结构中的标记改为0,表示该人已被扔下海了。这样循环计数直到有15个人被扔下海为止。

约瑟夫问题一般形式

编辑
约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。例如N=6,M=5,被杀掉的顺序是:5,4,6,2,3,1。
分析:
(1)由于对于每个人只有死和活两种状态,因此可以用布尔型数组标记每个人的状态,可用true表示死,false表示活。
(2)开始时每个人都是活的,所以数组初值全部赋为false。
(3)模拟杀人过程,直到所有人都被杀死为止。

约瑟夫问题pascal代码1

 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var 
  a:array [1..20of integer;
  n,m,i,j,k,n1,m1:integer;
begin
readln(m,n);
for i:=1 to do
  a[i]:=i;
m1:=m;
n1:=1;
while m1>0 do
begin
  j:=(n+n1-1-1mod m1 +1;
  n1:=j;
  m1:=m1-1;
  writeln(a[j]);
  for k:=j to m1 do
    a[k]:=a[k+1];
end;
end.

约瑟夫问题C++代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include
using namespace std;
main()
{
    bool a[101]={0};
    int n,m,i,f=0,t=0,s=0;
    cin>>n>>m;
    do
    {
        ++t;//逐个枚举圈中的所有位置
        if(t>n)
            t=1;//数组模拟环状,最后一个与第一个相连
        if(!a[t])
            s++;//第t个位置上有人则报数
        if(s==m)//当前报的数是m
        {
            s=0;//计数器清零
            cout‘ ‘;//输出被杀人编号
            a[t]=1;//此处人已死,设置为空
            f++;//死亡人数+1
        }
    }while(f!=n);//直到所有人都被杀死为止
}
c++代码2
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include 
#include 
#include 
using namespace std;
int main()
{
    int a[8],i,t,k;
    for(i=1;i
    {
        a[i]=1;
    }
    t=8;
    k=0;
    while(t>0)
    {
        for(i=1;i
        {
            if(a[i]==1)
            {
                k++;
                if(k==5)
                {
                    k=0;
                    a[i]=0;
                    cout
                    t--;
                    if(t==0)
                    {
                        break;
                    }
                }
            }
        }
    }
    return 0;
}
无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂度高达O(nm),当n,m非常大(例如上百万,上千万)的时候,几乎是没有办法在短时间内出结果的。我们注意到原问题仅仅是要求出最后的胜利者的序号,而不是要读者模拟整个过程。因此如果要追求效率,就要打破常规,实施一点数学策略。
为了讨论方便,先把问题稍微改变一下,并不影响原意:
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。
我们知道第一个人(编号一定是(m-1)mod n) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m mod n的人开始):
k k+1 k+2 ... n-2,n-1,0,1,2,... k-2
并且从k开始报0。
我们把他们的编号做一下转换:
k --> 0
k+1 --> 1
k+2 --> 2
...
...
k-2 --> n-2
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x‘=(x+k) mod n
如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况 ---- 这显然就是一个倒推问题!好了,思路出来了,下面写递推公式:
令f表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]
递推公式
f[1]=0;
f[i]=(f[i-1]+m) mod i; (i>1)
有了这个公式,我们要做的就是从1-n顺序算出f的数值,最后结果是f[n]。因为实际生活中编号总是从1开始,我们输出f[n]+1
由于是逐级递推,不需要保存每个f,程序也是异常简单:

约瑟夫问题pascal代码2

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var
  n,m,i,s,p:integer;
  a:array[1..10000of integer;
begin
  read(n,m);//这步不用说了吧?
  for i:=1 to do
    a[i]:=1;//先全部赋值1
  p:=0;s:=0;//统计人数和报数字用的
  repeat
    for i:=1 to do
    begin
    if a[i]=0
    then   continue; //用于等会排除出圈者     
    s:=s+a[i];//不断累加(报数字) 
    if s=m then//出圈者 
    begin
      write(i,‘ ‘);打印出圈者;
      a[i]:=0;//明白刚才continue的意思了吧
      p:=p+1;//人数减少一个;
      s:=0;//重头报起.
    end;                  
    end;
  until p=n;//直到人数到了
end.

约瑟夫问题pascal代码3

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Var
  a:array[1..100of integer;
  n,m,i,j,p:integer;
Begin
  write(‘Input n,m:‘);
  readln(n,m);
  for i:=1 to do
    a[i]:=i;
  p:=1;                     {p用于记录报数的位置}
  for i:=1 to do
    begin
      j:=0;                  {j用于记录报到的人数}
      while jdo
        begin
          if a[p]0 then j:=j+1;
          if p=n then p:=1 else p:=p+1;  {处理边界情况}
        end;
      if p1
           then begin write(a[p-1],‘ ‘);a[p-1]:=0;end       {处理边界情况}
           else begin write(a[n],‘ ‘);a[n]:=0;end;
    end;
end.

约瑟夫问题pascal代码4

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Var
  a:array[1..100of integer;
  n,m,p,i,j:integer;
Begin
  readln(n,m);
  for i:=1 to n-1 do
   a[i]:=i+1;
  a[n]:=1;
  p:=n;
  for i:=1 to do
    begin
      for j:=1 to m-1 do
         p:=a[p];
      write(a[p],‘ ‘);
      a[p]:=a[a[p]];
   end;
End.

约瑟夫问题c++

?
1
2
3
4
5
6
7
8
9
10
#include 
using namespace std;
const int m = 3;
int main()
{
    int n, f = 0;
    cin >> n;
    for (int i = 1; i 
    cout 
}

约瑟夫问题pascal代码5

?
1
2
3
4
5
6
7
8
var n,m,i,s:integer;
begin
write(‘N M =‘);
read(n,m);
for i:=2 to do
s:=(s+m) mod i;
writeln(‘The winner is ‘,s+1);
end.
这个算法的时间复杂度为O(n),相对于模拟算法已经有了很大的提高。算n,m等于一百万,一千万的情况不是问题了。可见,适当地运用数学策略,不仅可以让编程变得简单,而且往往会成倍地提高算法执行效率。

约瑟夫问题python代码

该程序基于python3.x实现
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#控制参数:
 
nums = 41
call = 3
 
#参数定义:
peoples = []
for in range(nums):
    peoples.append(True)
 
result = []
num =1
#主逻辑
while(any(peoples)):
    for index,people in enumerate(peoples):
        if people:
            if num == call:
                peoples[index] = False
                result.append(index+1)
#                print(index+1)#每轮的出局者                
#                print(peoples)#每次的队列状态
                num = 1               
            else:
                num += 1
print(‘-‘* 25)
print(‘\n总数为%d,报数为%d‘ % (nums,call))        
print(‘约瑟夫序列为:\n%s\n‘ % result)      
print(‘-‘* 25)

约瑟夫问题约瑟夫问题10e100版(from vijios)

描述 Description
n个人排成一圈。从某个人开始,按顺时针方向依次编号。从编号为1的人开始顺时针“一二一”报数,报到2的人退出圈子。这样不断循环下去,圈子里的人将不断减少。由于人的个数是有限的,因此最终会剩下一个人。试问最后剩下的人最开始的编号。
输入格式 Input Format
一个正整数n,表示人的个数。输入数据保证数字n不超过100位。
输出格式 Output Format
一个正整数。它表示经过“一二一”报数后最后剩下的人的编号。
样例输入 Sample Input
9
样例输出 Sample Output
3
时间限制 Time Limitation
各个测试点1s
注释 Hint
样例说明
当n=9时,退出圈子的人的编号依次为:
2 4 6 8 1 5 9 7
最后剩下的人编号为3
初见这道题,可能会想到模拟。可是数据实在太大啦!!
我们先拿手来算,可知n分别为1,2,3,4,5,6,7,8...时的结果是1,1,3,1,3,5,7,1...
有如下规律:从1到下一个1为一组,每一组中都是从1开始递增的奇数,且每组元素的个数分别为1,2,4...
这样就好弄了!!
大体思路如下:
①read(a)
②b:=1,c:=1{b为某一组的元素个数,c为累计所加到的数}
③while c
⑥c:=c-b{退到前一组}
⑦x:=a-c{算出目标为所在组的第几个元素}
⑧ans:=x*2-1{求出该元素}
⑨write(ans)
有了思路,再加上高精度就可以了。我写的代码比较猥琐,因为是先把上面的思路敲进去,再写过程,又把一些简单的过程合到主程序中了,所以有点乱,也有点猥琐。起提供思路的作用还是完全可以的吧~~~
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
var
  a,b,c:array[1..105]of longint;
  la,lb,lc,i:longint;
  s:string;
procedure incc;
var
  i:integer;
begin
  for i:=1 to 105 do
    c:=c+b;
  for i:=1 to 104 do
    if c>9 then
             begin
               c:=c+cdiv10;
               c:=c mod 10;
             end;
end;
functioncxiaoa:boolean;
vari:integer;
begin
cxiaoa:=false;
fori:=105downto1do
ifctrue;break;end
elseifc>athenbreak;
end;
proceduredoubleb;
vari:integer;
begin
fori:=1to105dob:=b*2;
fori:=1to104doifb>9then
begin
b:=b+bdiv10;
b:=bmod10;
end;
end;
proceduredecc;
vari,j:integer;
begin
fori:=1to104do
ifc>=bthenc:=c-belse
begin
j:=i+1;
whilec[j]=0doinc(j);
whilej>ido
begin
c[j]:=c[j]-1;
c[j-1]:=c[j-1]+10;
dec(j);
end;
c:=c-b;
end;
end;
procedurefua;
vari:integer;
begin
fori:=1to104do
ifa>cthena:=a-celse
begin
a:=a-1;
a:=a+10;
a:=a-c;
end;
end;
procedureoutit;
vari,j:integer;
begin
fori:=1to105doa:=a*2;
fori:=1to104doifa>9then
begin
a:=a+adiv10;
a:=amod10;
end;
ifa[1]>0thena[1]:=a[1]-1else
begin
j:=2;
whilea[j]=0doinc(j);
whilej>1do
begin
a[j]:=a[j]-1;
a[j-1]:=a[j-1]+10;
dec(j);
end;
a[1]:=a[1]-1;
end;
fori:=105downto1doifa>0thenbeginj:=i;break;end;
fori:=jdownto1dowrite(a);
end;
begin
readln(s);
la:=length(s);
fori:=ladownto1doa:=ord(s[la+1-i])-ord(‘0‘);
b[1]:=1;
c[1]:=1;
whilecxiaoado
begin
doubleb;
incc;
end;
decc;
fua;
outit;
end.

约瑟夫问题猴子选王

编辑

约瑟夫问题问题表述

一. 问题描述:
一堆猴子都有编号,编号是1,2,3 ...m,这群猴子(m个)按照1-m的顺序围坐一圈,从第1开始数,每数到第N个,该猴子就要离开此圈,这样依次下来,直到圈中只剩下最后一只猴子,则该猴子为大王。
约瑟夫
"密码问题"
问题描述:编号为1、2、3、...、N的N个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。从指定
编号为1的人开始,按顺时针方向自1开始顺序报数,报到指定数M时停止报数,报M的人出列,并将
他的密码作为新的M值,从他在顺时针方向的下一个人开始,重新从1报数,依此类推,直至所有的
人全部出列为止。请设计一个程序求出出列的顺序,其中N≤30,M及密码值从键盘输入。
二. 基本要求:
(1) 输入数据:输入m,n m,n 为整数,n
(2)中文提示按照m个猴子,数n 个数的方法,输出为大王的猴子是几号 ,建立一个函数来实现此功能

约瑟夫问题编程解决

  • 1.C程序
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include 
#include 
#defineLENsizeof(structmonkey)//定义structmonkey这个类型的长度
struct monkey
{
int num;
struct monkey *next;
};
struct monkey *create(int m)
{
struct monkey *head,*p1,*p2;
inti;
p1=p2=(struct monkey*)malloc(LEN);
head=p1;
head->num=1;
for(i=1,p1->num=1;i
{
p1=(struct monkey*)malloc(LEN);
p1->num=i+1;
p2->next=p1;
p2=p1;
}
p2->next=head;
return head;
}&l


评论


亲,登录后才可以留言!