题解-猴子选大王(约瑟夫环)

题目描述


在m只猴子聚在一起选大王, 商定规则如下: 大家围成一圈, 按顺时针从1编号, 第一次从编号为1的开始报数, 以后循环进行, 当报到n时退出圈子, 下一只则重新从1开始报数, 圈子中剩下的最后一只猴子则为大王.

有多组测试数据. 输入的第一行是整数T(1<=T<=100), 表示随后测试数据的组数. 每组测试数据占一行, 由正整数m和n组成, 两数之间有一个空格. 1 <= m,n <= 200.

对应每组测试数据, 输出选出的大王的猴子编号.


输入数据
4
3
1
20
20


输出数据
3
1
20
20

思路1:这个就是约瑟夫环问题,考虑到猴子围成圈坐,不分首尾,这个结构很像循环链表。尝试用循环链表模拟。

#include<stdio.h>
#include<stdlib.h>
typedef struct circlel{
    int number;
    struct circlel* next;
}circlel;
int index=0; //全局变量,用于统计剩余猴哥数量
typedef circlel* headp; //头指针
headp init_circle(){//寻常的创造头指针
    headp phead=malloc(sizeof(circlel));
    if(!phead){
        printf("初始化失败\n");
        exit(0);
    }
    phead->number=1;
    phead->next=phead;//唯一和普通链表不同的点,就是尾节点要连上头节点
    index=1;
    return phead;
}
void creat_cirl(headp phead,int num){//寻常的创造节点
    circlel* newnode=malloc(sizeof(circlel));
    if(!newnode){
        printf("内存不够 创建失败\n");
        return;
    }
    circlel* cur=phead;
    while(cur->next!=phead){
        cur=cur->next;
    }
    newnode->next=phead;
    cur->next=newnode;
    newnode->number=num;
    index++;
}
//这里删除的逻辑不是按储存的数值来删,而是删除头指针(这道题里面头指针位置会变)后面第num个节点
circlel* destory_cirl(headp phead,int num){
    if(!phead){
        printf("不存在");
        return NULL;
    }
    circlel* cur=phead;
//num为零表示删除当前当前头指针指向的头节点,用于应对每次只跳过1的情况下,第一次出局的情况
    if(num==0){
        while(cur->next!=phead)
        cur=cur->next;
    }
    else if(num>0)
//找到num前面那个节点
    while(num-1){
        cur=cur->next;
        num--;
    }
//寻常的删除操作
    circlel* delete=cur->next;
    cur->next=delete->next;
    index--;
    free(delete);
//返回cur还是cur->next无所谓,后面删除算法有细微区别
    return cur;
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
    index=0;
    headp phead=init_circle();
    int m,n;
    scanf("%d %d",&m,&n);
    for(int i=1;i<m;i++){
        creat_cirl(phead,i+1);
    }
//设计k是因为第一次头指针已经指向第一只猴子了,只需要往前n-1。对应前面删除函数的num==0情况。
//从第二次以后,头指针变成删除的猴子的前面一只猴子,所以重新变为n
    int k=1;
    while(index!=1){
//注意这里头指针要变,指向出局的猴子的下一个猴子
        phead=destory_cirl(phead,n-k);
        k=0;
    }
    printf("%d\n",phead->number);
    free(phead);
}
    
}

思路2:造一个循环链表太麻烦了,直接用数组模拟。

#include<stdio.h>
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int m,n;
        scanf("%d%d",&m,&n);
//left表示剩余猴子,a[m]储存猴子编号,编号赋为零表示出局。
        int left=m;
        int a[m];
        for(int i=0;i<m;i++)a[i]=i+1;
//需要从-1开始+n,模拟第一只猴子的上一只猴子,才符合题意。
        int number=-1;
        while(left!=1){
            int start=number;
            number=(number+n)%m;
//zero_found是能用数组模拟的关键
//zero_found查找下n个猴子之间有多少个0,即出局的猴子,这些猴子消耗了n,后面要加回来
//找到一个0,zero_found++,i的遍历次数+1
            int zero_found=0;
            for(int i=0;i<n+zero_found;i++){
                if(a[(start+1+i)%m]==0)
                zero_found++;
            }
//找到实际上的后n只猴子编号,number应该加上出局的猴子
            number+=zero_found;
            number%=m;
        a[number]=0;
        left--;
        }
//输出胜者,即唯一不为零的数
        for(int i=0;i<m;i++)
            if(a[i]!=0)printf("%d\n",a[i]);
    }
    return 0;
}
posted @ 2025-04-15 19:53  hardestnut  阅读(247)  评论(0)    收藏  举报