有n个人围成一圈,顺序排号。从第一个人开始报数(从1到3报数),凡是报到3的人退出圈子,问最后留下来的是原来的第几位(指针方法处理)。

1、前言
一开始我感觉这道题应该蛮简单的,不就是到第三个人那不报数嘛,然后问最后一个留下的人原来排在第几号。经过博主仔细阅读之后,发现,事情并非如此简单。因为,他说了,每到第三个人那就退出,也就是说,在原来元素顺序的基础上,要从第3,6,9,12等三的倍数之后的序号退出该排序(此时博主还没系统学过数据结构,只是稍微了解了一点数据结构的知识,所以咱只用现有的C/C++语言的知识来借题就可)。
2、解体思路
上面已经说了,首先咱得从每到第三号位的时候,退出这个圈子;
其次,退出去之后,后面的元素得依次向前移一位。因为,前面的3已经退出了,而问题又问的是这个位置上的数原来排在第几号,所以我们得把后面的元素都依次向前移。
最后,光是向前移还不行,还必须在移的时候用一个数组来记录所移元素的初始位置。
3、
好,说了那么多,接下来就直接看代码

#include <iostream>
using namespace std;

int main()
{
    void sort(int*, int);
    cout << "请输入N个人" << endl;
    int number;
    int n = 0;//用n来记录退出的次数
    cin >> number;//输入的人数
    cout << "请输入每个人的数字代号"<<endl;
    int* p =new int[number];//申请一个可以存入输入的人数的动态数组
    for (int i = 0;i < number;i++) {
        cin >> *(p+i);//输入人的代号
    }    
    int* mark = new int [number];//用来标记该元素改变前的位置
    for(int i=0;i<number;i++)
    {
        mark[i] = i;
    }
    for (int i = 0;i < number;i++)
    {
        if ((i + 1) % 3 == 0) {//如果到第三个人就退出
            p[i - n] = -1;//用-1来提示退出圈子
            sort(p + i - n, number - i);//调用sort函数来退出第三个人,函数值从第i个人开始,余下number-1-i个人
            sort(mark + i - n, number - i);
            n = n + 1;//记录改变次数
        }
    }
    for (int i = 0;i < number;i++) {//输出改变之后的排序
        if (p[i] == -1) break;//输出到-1时直接退出循环
        cout << p[i] << " ";//输出数组
    }
    cout << endl;
    for (int i = 0;i < number;i++) {//输出现有排序的元素的原有位置
        if (p[i] == -1)break;
        cout << " " << p[i] << "原有的位置是:" << mark[i] << endl;
    }
    delete[]p;//清空指针数组
    delete[]mark;
    return 0;
}

void sort(int* p,  int n) {//用p来接受数组p+i的首地址;这里的n是用来接收该次退出的第三位元素之后的元素总数
    int *t=new int[n-1];//申请一个存n-1个人的动态数组,该数组用来记录所接受的数组的第p+i+1位之后的元素
    int z;//申请一个用来将数组的第p+i位与最后一位数转换的变量
    if (n != 1) {//使用这个控制条件的原因是因为当n=1时,调用函数的数组访问的是最后一个有效元素(不等于1的元素)
        for (int i = 0;i < n - 1;i++) { *(t + i) = *(p + i + 1); } //将数组的p+i+1之后的地址存的值都传给数组t
        if (*p == -1) {//将所接受的数组的地址的值与数组末尾的地址的值交换,将-1放在数组的末尾
            z = *p;
            *p = *(p + n - 1);
            *(p + n - 1) = z;
        }
        for (int i = 0;i < n - 1 ;i++) { *(p + i) = *(t + i); }//将t存入的值再传回给数组p+i之后的值,到第n-1个结束
    }
    delete[]t;//清空指针
}

在这里插入图片描述
好啦,整个代码已经写完了,这也是靠现有知识花了两天时间才做出来了这道题(中途遇到了蛮多bug的,看来是真的没天赋了哈哈哈),不过,通过自己一手做出来的时候,那感觉还不错。

### 回答1: 题目:有n个人围成一圈顺序排号。从第一个人开始报数(从1到3报数),凡是报到3的人退出圈子最后留下的是原来第几号的那位。 解题思路:约瑟夫题 分析题目,可以得出一个递推公式。设 f(i, 2) 为 i 个人,每报到一次就退出个人最后剩下的人在围成一个圈。则最终求得的结果为 f(n, 2)。当只有一个人时,该人即为最后剩余的人,此时 f(1, 2) = 1。对于其他 n 个人,每报数到 3 的人退出时,其实就是下一个开始报数的人 1 号相对位置向前移动了 3 位,由于是环,所以需要对总人数取模。 即有递推公式 f(i, 2) = (f(i-1, 2) + 3) % i,初始条件为 f(1, 2) = 1。 代码如下: ### 回答2: 这道题是一个经典的约瑟夫题(Josephus problem),在数学上被广泛讨论和应用。 我们可以通过模拟的方法来解决这个题。假设有n个人,他们按照顺序从1到n编号,围成一圈。我们从第一个人开始报数,每报到3的人就离开圈子,直到剩下最后个人为止。 我们可以用一个列表来模拟这个过程。首先,将所有n个人的编号加入到列表中。然后,我们定义一个报数指针pos,初始值为0,表示第一个人。接着,我们从列表中依次取出编号,每取出一个编号,pos加1,表示下一个人。当pos等于3时,将该编号从列表中删除,pos重新置为0。重复这个过程,直到列表中只剩一个编号为止。 最后剩下的那个人的编号即为答案。代码如下: ```python n = int(input("请输入人数:")) nums = list(range(1, n+1)) pos = 0 while len(nums) > 1: pos = (pos + 2) % len(nums) nums.pop(pos) print("最后留下的是原来第{}号的那位".format(nums[0])) ``` 其中,`pos = (pos + 2) % len(nums)`是关键代码,表示每报到3的人就从列表中删除。具体地,pos指向的是下一个要删除的人的位置,每删除一个人,pos加2。但是,由于删除了一个人后,列表的长度减1,所以我们需要对pos取模,使其始终在0到len(nums)-1之间。 ### 回答3: 这是经典的约瑟夫题,可以用数学方法或者模拟方法来解决。 数学方法: 假设最终剩下的人的编号为f(n),则f(n)在第一次报数时的编号为(1+3-1)%n+1=3%n+1。 第一轮结束后,第3个人已经出圈了,剩下n-1个人,此时重新以编号为1开始报数,假设第二次结束后剩下的人的编号为f(n-1), 则f(n)=(f(n-1)+3)%n+1。 最终当n=1时,只剩下一个人,其编号即为f(1)=1。 模拟方法: 可以使用一个列表来表示当前围成一圈的人,从第一个人开始报数,每次报数到3时,将对应的人从列表中移除, 直到只剩下一个人为止,最后输出其编号即可。 下面给出Python代码实现: # 模拟方法 def josephus(n): people = list(range(1, n+1)) i = 0 while len(people) > 1: i = (i+2) % len(people) people.pop(i) return people[0] print(josephus(10)) # 输出: 5 # 数学方法 def josephus_math(n): res = 1 for i in range(2, n+1): res = (res+3) % i + 1 return res print(josephus_math(10)) # 输出: 5
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值