题解-猴子选大王(约瑟夫环)
题目描述
在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;
}

浙公网安备 33010602011771号