用能看的懂的方法模拟约瑟夫环

本文详细解析了约瑟夫环问题,通过一维数组存储和遍历的方式实现了该问题的算法。介绍了如何使用for循环和while循环来处理约瑟夫环中的人员出列过程,并讨论了算法实现中的难点。

约瑟夫环:

已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;
他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。

知识点:
1. 一维数组的赋值 遍历和存储
2.for循环 , while 循环


难点:
1.出列一圈后,剩下人的该怎么标记 和出列 , 才不会出现重复和漏掉


代码实现:

#import <Foundation/Foundation.h>


//从编号为k的人开始报数

#define K 1


// 数到M的人出列

#define M 4


// 总人数N

#define N 15



int main(int argc,const char * argv[]) {

    

    // 存在一个数组里面

    int p[N] = {0};

    

    // 这个数组用来保存出列的人

    int b[N] = {0};

    

    // 定义变量

    int count = 0; // 计数器 数到M个人后出列

    

    int num = 0; // 循环结束计数器 保证N个人全出来后停止循环

    

    int index = 0;// 定义一个变量记录存储位置

    

    // 让人拿上个随机的编号10 ~ 100,看到这个数就代表这个人

    for (int i = 0; i < N; i++) {

        

        p[i] = arc4random() % (100 - 10 + 1) + 10;

        

        printf("%d\t" , p[i]);

    }

    

    // 人没有出来完就一直循环

    while (num < 15) {

        

        for (int i = 0; i < N; i++) {

            

            if (p[i] != 0) {

                count++;

                

                //如果 count = M出列,报道这个数的人出列

                if(count == M){

                    

                    // 将出圈的元素按序存储在b

                    b[index++] = p[i + K - 1];

                    

                    // 将出圈的元素赋值为 0 ; 以后不在记录

                    p[i] = 0;

                    

                    // 出圈计数器count重置为0;

                    count = 0;

                    

                    // 出列计数器 num +1

                    num++;

                }

            }

        }

    }

    

    printf("\n出列顺序为:\n------------------------------------\n");

    

    // 打印出圈的值

    for (int i = 0; i < N; i++) {

        

        printf("%d\t" , b[i]);

   

    }

    

    return 0;

}









<think> 我们正在分析的代码片段是约瑟夫环问题的一个实现。在用户提供的代码中,有一部分是左移数组的操作。我将解释这一操作的原因和逻辑。 ### 约瑟夫环问题背景 约瑟夫环问题描述:$M$个人围成一圈,从第一个人开始报数,报到第$N$个的人出圈,然后从下一个人重新开始报数,直到圈中只剩下一个人。问题要求输出出圈的顺序以及最后留下的人。 ### 左移数组操作的目的 在修正后的代码中,左移数组的操作出现在将报数为$N$的人移除之后。具体代码段如下: ```c if (j < i - 1) { for (k = j; k < i - 1; k++) { *(p1 + k) = *(p1 + k + 1); // 左移后续元素 } } ``` 这里: - `i`:当前剩余的人数(在循环中,`i`从$M$递减到$2$)。 - `j`:当前报数到$N$的人的位置(索引)。 #### 原因解释 1. **模拟移除操作**:当一个人被移除(出圈)后,我们需要在数组中删除该元素。数组在内存中是连续存储的,删除一个元素后,为了保持数组的连续性,需要将后面的元素向前移动(左移)一位,覆盖被移除的元素。这样,数组的长度就减少了$1$,并且剩余元素仍然占据数组的前$i-1$个位置。 2. **维持数组结构**:移除元素后,数组的大小减$1$。左移操作确保了从位置`j`开始,后面的元素依次向前移动,这样数组的前$i-1$个位置就是当前剩余的人。这样,下一轮报数时,我们可以继续使用这个数组(长度为$i-1$)来表示圈中的人。 3. **调整索引`j`**:在左移之后,原本位于`j+1`的元素移动到了`j`,`j+2`的元素移动到了`j+1`,以此类推。因此,下一轮报数的起始位置需要调整: - 如果移除的是最后一个元素(即`j == i-1`),那么移除后,下一轮报数从数组开头(位置$0$)开始。因此,我们将`j`重置为$0$。 - 如果移除的不是最后一个元素(`j < i-1`),那么移除元素后,原本在`j`后面的元素都向前移动了一位,因此下一轮报数应该从当前位置`j`开始(因为该位置已经被下一个元素占据)。注意,此时我们不需要改变`j`的值,因为左移操作后,`j`位置已经是原来`j+1`位置的人,而下一轮报数应该从被移除者的下一个人开始。由于左移后,`j`位置已经是下一个人的位置,所以下一轮报数就从这里开始。 #### 示例说明 假设当前数组`a`为(剩余5人): ``` 索引: 0 1 2 3 4 数值: 1 2 3 4 5 ``` 当前`i=5`,经过报数后,假设`j=2`(即报数到第3个人,数值为3的人被移除)。那么: 1. 将`a[2]`(即3)存入数组`b`(出圈顺序)。 2. 因为`j=2`(小于`i-1=4`),所以进行左移:将索引3和4的元素向前移动一位。 - 移动后数组变为: ``` 索引: 0 1 2 3 数值: 1 2 4 5 ``` 注意,原来索引3的值4移到了索引2,索引4的值5移到了索引3。 3. 下一轮报数,`i`变为4,而`j`保持不变(仍然是2)。但此时数组索引2位置的值是4(原来索引3的值),所以下一轮报数从4开始。 ### 为什么需要左移? 如果不进行左移,那么数组中会留下一个“空洞”(被移除的位置),而下一轮报数时,我们会跳过这个空洞吗?实际上,如果不左移,数组的大小并没有减少,我们无法知道哪些位置是有效的。因此,左移操作是为了: - 保持数组的连续性,使得剩余的人占据数组的前$i-1$个位置(当前剩余$i-1$人)。 - 下一轮报数时,我们只需要从$0$到$i-2$(因为数组索引从0开始,且当前剩余$i-1$人)的范围内报数。 ### 总结 左移数组操作是约瑟夫环问题中模拟移除一个人的关键步骤。它通过覆盖被移除的元素位置,将后续元素前移,从而动态地维护了剩余人员的数组。这样,每一轮报数都可以在一个连续且无空洞的数组中进行,数组的大小随着每一轮移除一个人而逐渐减小。 ### 相关问题 1. 约瑟夫环问题中,除了使用数组移位,还有哪些数据结构可以实现? 2. 如果使用链表实现约瑟夫环,左移操作是否还需要?为什么? 3. 数组移位的时间复杂度是多少?如何优化约瑟夫环问题的时间复杂度? </think>在约瑟夫环问题代码中,**左移数组操作的核心目的是动态维护剩余人员列表**。这本质上是在模拟人员出圈后剩余人员重新排列的过程。以下是详细解释: ### 左移操作的必要性 1. **维持环形结构连续性**: - 当第$j$个位置的人出圈后,数组出现"空洞" - 左移操作通过`*(p1+k)=*(p1+k+1)`将后续元素向前移动一位 - 这保持了剩余人员在数组前部的连续存储 2. **位置索引的修正**: ```c if (j < i - 1) { // 当移除的不是最后一个元素时 for (k = j; k < i - 1; k++) { *(p1 + k) = *(p1 + k + 1); // 关键左移操作 } } ``` - 将数组`a`中`j+1`到`i-1`的元素向前覆盖 - 确保下一轮报数时`j`位置已是新的有效人员 3. **模拟物理出圈行为**: - 在真实约瑟夫环中,人员出圈后圆圈会收缩 - 左移操作在数组中实现了这种"圆圈收缩"的数学等价 - 使下一次报数位置`j`能正确指向后续人员 ### 操作流程示例 假设$M=5$,$N=3$,当前数组状态: ``` 索引: 0 1 2 3 4 人员: 1 2 [3] 4 5 // j=2 (准备移除3) ``` 左移后: ``` 索引: 0 1 2 3 人员: 1 2 4 5 // 位置3的元素5移到位置2 ``` 此时: 1. 移除人员3的记录已存入`b` 2. 下一轮报数从位置`j=2`开始(原位置3的4号人员) 3. 数组长度`i`减1,完成一轮更新 ### 算法复杂度分析 | 操作 | 时间复杂度 | 空间复杂度 | |------|-----------|-----------| | 左移数组 | $O(n)$ | $O(1)$ | | 整体算法 | $O(n^2)$ | $O(n)$ | 这种实现方式虽然时间复杂度较高,但优点在于: 1. 直观模拟了物理过程[^1] 2. 避免使用复杂数据结构 3. 内存访问局部性好 ### 相关问题 1. 约瑟夫环问题能否用数学公式直接求解而无需模拟? 2. 使用链表实现约瑟夫环时如何避免左移操作? 3. 当$M$极大时如何优化时间复杂度? 4. 约瑟夫环问题有哪些实际应用场景?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值