约瑟夫环的C语言数组实现(仍需细嚼慢咽…)

快速查看链接~

引子

据说著名犹太历史学家和军事学家约瑟夫有过以下的故事:在罗马人占领乔塔帕特以后,39个犹太人与约瑟夫及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephu、和他的朋友并不想遵从,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,最终逃过了这场死亡游戏。

这个故事里,为什么最后,根据总人数和这些规则,我们会得到一个出圈序列表,最后恰恰只剩下原来的第16个和第31个位置,这不是巧合这里面隐含的数学问题我们称为约瑟夫环问题

同样还有一个类似的例子—猴子选大王M只猴子要选大王,选举办法如下:所有猴子按1…M编号围坐一圈,从第1号开始按顺序1,2,...,N报数,凡报到N的猴子退出到圈外,再从下一个猴子开始继续1到N继续报数,如此循环,直到圈内只剩下一只猴子时,这只猴子就是大王这个问题也是有约瑟夫环延伸出来的,也称为约瑟夫环问题。

  • 引入问题

编号为 1,2,3,…,n 的 n 个人围坐一圈,任选一个正整数 m 作为报数上限值,从第一个人开始按顺时针方向报数,报数到 m 时停止,报数为 m 的人出列。从出列人的顺时针方向的下一个人开始又从 1 重新报数,如此下去,直到所有人都全部出列为止。

  • 算法思想

每个人的编号存放在一个数组 a 中,主函数中决定人数的个数以及报数的上限值 m,设计一个函数实现对应的操作。函数的形参有整型数组 a、整数 n 和 m,n 用来接收传递的人数,m 用来接收报数上限,函数的返回值为空;函数体中输出出列人的顺序。

函数中利用循环访问数组中 n 个元素,每次访问元素,设定内循环连续访问 m 个元素,元素访问的下标为 k,访问到第 m 个元素时,如果元素不是 0,此时输出元素 a[k],再设定 a[k] 为 0,继续访问后面的元素。

主函数中设定数组 a,从键盘输入 n 和 m,利用循环产生 n 的位置序号存放到数组 a 中,调用函数实现相应的操作。

  • 具体实现
/*****************************************************
copyright (C), 2014-2015, Lighting Studio. Co.,     Ltd. 
File name:
Author:Jerey_Jobs    Version:0.1    Date: 
Description:
Funcion List: 
*****************************************************/

#include <stdio.h>
#define N 100

int josef(int a[],int n,int m)
{
    int i,j,k=0;//i用于遍历所有人,j用于从哪儿开始报数,k用于访问淘汰者下标
    for(i=0;i<n;i++)//遍历一圈
    {
        j=1;//从1开始报数
        while(j<m)//直到报到上限值为止
        {
            while(a[k]==0)//当且仅当全部报完时a[k]=0,->空数组.
			{
				k=(k+1)%n;//当且仅当报完时k=0
			}
			j++;
            k=(k+1)%n;//当前是否还能形成环
        }
        while(a[k]==0)//检查是否还有人没报完 
		{
			k=(k+1)%n;
			/*检测到最后下标置零
			因为每一次人员淘汰 都要
			从规则中设定的下一个位置从1开始报起*/
		}
        printf("%d ",a[k]);
        a[k]=0;//每出列一人,那个人位置元素置空(总要设立标志位检测还有多少人没淘汰)
    }
    return 0;
}

int main()
{
    int a[100];
    int i,j,m,n;
    
    printf("please input numbers of people and number off:");
    scanf("%d%d",&n,&m);
    
	for(i=0;i<n;i++)
        a[i]=i+1;//所有人顺序站成一圈
    
    printf("\n output:\n");
	josef(a,n,m);
    printf("\n");
    
    return 0;
}
  • 实验结果

 以下为输出111.txt文件

please input numbers of people and number off:
 41 3
output:
3 6 9 12 15 18 21 24 27 30 33 36 39 1 5 10 14 19 23 28 32 37 41 7 13 20 26 34 40 8 17 29 38 11 25 2 22 4 35 16 31 
please input numbers of people and number off:
 100 4
output:

4 8 12 16 20 24 28 32 36 40 44 48 52 56 60 64 68 72 76 80 84 88 92 96 100 5 10 15 
21 26 31 37 42 47 53 58 63 69 74 79 85 90 95 1 7 14 22 29 35 43 50 57 65 71 78 86 93 99 9 
18 27 38 46 55 66 75 83 94 3 17 30 41 54 67 81 91 6 23 39 59 73 89 11 33 51 77 98 25 61 87 19 62 2 49 13 82 70 97 45 34 
 

☆关于Linux输出重定向:

> 是定向输出到文件,如果文件不存在,就创建文件;如果文件存在,就将其清空;一般我们备份清理日志文件的时候,就是这种方法:先备份日志,再用`>`,将日志文件清空(文件大小变成0字节)。
>> 这个是将输出内容追加到目标文件中。如果文件不存在,就创建文件;如果文件存在,则将新的内容追加到那个文件的末尾,该文件中的原有内容不受影响。

 

  • 实验总结

① 程序由 main() 函数和 josef() 函数组成,main() 函数调用 josef() 函数,用数组名作为函数参数,在主函数和被调用函数中分别定义数组。主函数执行到 josef(a,n,m) 语句时,将数组 a 的首元素的地址传递给形参数组 a,程序转去执行 josef(),形参数组 a 中的元素发生逆序排列,则实参数组 a 也随之改变,当 josef() 执行完后,返回到主函数中继续执行被调函数后面的语句。

实例中定义函数 josef() 解决问题的难点有两个:一是如何求下一个出圈的人的位置;二是此人出圈后对这个人的位置如何处理。从第一个人开始报数,报到 m 时,此人出圈,设定变量 j,每次统计出圈的人数,当出圈人数到 m 时,重新开始统计。n 个人围坐一圈,可看作环状,设定 k 表示出圈人的下标,则出圈人的下标的计算可用“(k+l)%n”表示。对于第二个问题,首先将出圈人的位置打印输出,然后将其位置元素设置为 0。

③ 数组名作函数参数时,要求在被调用函数和调用函数中分别定义数组,且形参和实参必须是类型相同的数组。实参和形参数组是指向同一段地址空间的,当主函数执行时,这段空间由实参数组控制,当被调用函数执行时,这段空间由形参数组使用,被调函数执行结束后,该空间又交回给实参数组。

用数组名作为函数参数时,形参与实参之间的传递方式为地址传递,因此,形参数组的改变会影响实参数组的内容。

C 编译系统对形参数组的大小不做检查,只是将实参数组的首地址传给形参数组,所以形参数组可以不用指定大小。如实例中被调用函数的首部定义为 void josef(int a[], int n,int m),其中的整型数组 a 的定义为 int a[],没有给出数组的具体大小。

一维数组名、多维数组名都可以作为函数的参数,进行地址传递。

  • 参考文献

刘文锋. 约瑟夫环的C语言数组的实现[J]. 科技信息, 2010(21):592.

田艳. 用不同语言实现约瑟夫环问题[J]. 科技创新导报, 2007(12):104-104.

张谞晟. 约瑟夫环的C语言实现与应用[J]. 无线互联科技, 2017(6):141-142.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值