7-2 猴子选大王 (10分)链表法+数组法

7-2 猴子选大王

题目要求

7-2 猴子选大王 (10分) 一群猴子要选新猴王。新猴王的选择方法是:让N只候选猴子围成一圈,从某位置起顺序编号为1~N号。从第1号开始报数,每轮从1报到3,凡报到3的猴子即退出圈子,接着又从紧邻的下一只猴子开始同样的报数。如此不断循环,最后剩下的一只猴子就选为猴王。请问是原来第几号猴子当选猴王?

输入格式:
输入在一行中给一个正整数N(≤1000)。
输出格式:
在一行中输出当选猴王的编号。
输入样例:

11

输出样例:

7

让我们先来看看输入样例中11个猴子的选举情况:

1234567891011
第一轮12312312312
第二轮31231231
第三轮23123
第四轮123
第五轮12
第六轮31
第七轮2

所以最后7号猴子被选为大王。

链表法

很容易想到循环链表,每次循环只需将报数为3的猴子代表的节点从链表中删除(即题目中的退出圈子),使用双向或单向链表皆可,这里我使用单向循环链表。

  • 关于头结点,在链表中,使用头结点在操作上更方便,对于此题还有一个作用,可以将当前还在圈子的猴子个数存于头节点中,并以此作为是否继续遍历循环链表的依据。

节点内容以及循环链表示意如下图所示:
在这里插入图片描述
节点包含猴子的编号number、每次报数的号码data以及指向下一节点的next指针。
由于是单向链表,所以在找到data=3的节点再删除时,无法找到其前驱节点,所以当data=2时,就删除下一节点。

#include<stdio.h>
#include<malloc.h>
 
typedef struct Node{
	int number,data;
	struct Node *next;
} LinkList;

LinkList *create(int n){
	int i,num = 0;
	//定义头节点,普通节点,尾部节点;
	LinkList *head, *node, *p;
	//分配头节点地址
	head = (LinkList*)malloc(sizeof(LinkList));
	//若是空链表则头尾节点一样
	p = head;         
	head -> data = n;
	head -> number=0;
	for (i = 0; i < n; i++) {
		node = (LinkList*)malloc(sizeof(LinkList));
		node -> number = i + 1;
		node -> data = 0;
		p -> next = node;
		p = node;
	}
	//结束创建,构造循环链表 
	p -> next = head -> next;
	return head;
}

int main(){
	int i,n,num = 0;
	scanf("%d",&n);
	
	LinkList *p,*head = create(n);
	p = head -> next;
	int count = head -> data;
	while(count != 1){
		p->data = (num++) % 3 + 1;
		printf("%d-%d\n",p->number,p->data);
		if(p -> data == 2){
			p -> next = p -> next -> next;
			num+=1;
			count--;
		} 
		p = p -> next; 
	}
	printf("%d",p -> number);
	free(p);
	free(head);	
	return 0;
} 

数组法

如果不采用链表法也可以使用一个2维数组来解决,首先需要重新分析选出猴子大王过程。

分析:

在第一轮中淘汰3号、6号、9号,均为3的倍数,可以拓展思路,在第二轮报数中,表面上淘汰报数为3的猴子,实际上淘汰累计报数为3的倍数的猴子,下图更加直观地展现了这一过程。

在这里插入图片描述
图中括号中的数字代表累计报数,可以发现11只猴子中选出1个大王,淘汰的为累计报数为3的倍数的前10只猴子,即:3,6,9,12,15,18,21,24,27,30。

构造数组

这里二维数组的列数即为猴子个数,很好确定。但是行数因猴子个数的不同会有变化,具体行数可能可以通过数学公式计算,或者直接构造行数一个足够大的数组。数学方式确定行数的方法暂时我还没找到,读者有这方面的想法可以在评论区讨论;足够大的数组会造成不必要的空间浪费。所以秉持“环保”理念,这里只需构造两行的二维数组,每行都存储报数的数据,至于猴子本身的编号可根据下标确定,循环将后来的数据覆盖前面的数据即可。

数组初始化
为了与之后的判断相区别,这里将下标为0的列初始化为猴子编号,下标为1的列全部初始化为 -1

下标012345678910
01234567891011
1-1-1-1-1-1-1-1-1-1-1-1
  • 交替插入:遍历插入第0行后,遍历插入第1行,然后再遍历插入第1行,以此类推,节约空间。
    这种操作类似于有刷电机的原理,每次插入的行与之前插入的相反。代码中以 reset=!reset 来实现。

对于 第一轮 循环:向 下标为 1 的行插入数字,并判断 上一行 相同列数字是否为3的倍数,若是,则插入0,否则插入的数字按序增长。

下标012345678910
01234567891011
11213014150161701819

对于 第二轮 循环:向 下标为 0 的行插入数字,并判断 上一行 相同列数字是否为3的倍数,若是,则插入0,否则插入的数字按序增长。

下标012345678910
00200210022230024
11213014150161701819

之后的循环以此类推。

  • 循环的退出条件: 由题意可知,当最后圈内猴子个数剩余1个时,即选出了大王。
    所以每当有新的3的倍数的累计报数产生,就相应地减少猴子个数。注意这里是新的3的倍数的累计报数,对于之间上一行已经是0的列来说,猴子个数count保持不变。
    即:

if(当前插入数字为3x && 上一行该列数字不为0){ count - -; }
if(当前插入数字为3x && 上一行该列数字为0) { count保持不变; }

完整代码如下:

#include<stdio.h> 

int main(){
	int i,n;
	scanf("%d",&n);
	int king[2][n];
	//二维数组初始化
	for(i=0;i<n;i++){
		king[0][i]=i+1;
		king[1][i]=-1;
	}
	int count=n,last=n,reset=0,flag;
	i=0;
	while(count!=1){
		for(i=0;i<n;i++){
			if(king[reset][i]%3==0 && king[!reset][i]%3==0){
				king[!reset][i]=0;
			}else if(king[reset][i]%3==0 && king[!reset][i]%3!=0){
				king[!reset][i]=0;
				count--;
			}else {
				king[!reset][i]=++last;
				flag=i+1;
			}
		}
		//交替插入
		reset=!reset;
	}
	printf("%d",flag);
	return 0;
}

通过添加输出语句可以显示每一轮的选举以及完整的选举过程:

每一轮选举结果:

在这里插入图片描述
完整选举过程:

在这里插入图片描述

  • 6
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
#include "stdafx.h" #include using namespace std; //声明结构:作为链表的节点 struct Monkey { //数据域 int Num = 0; //指针域 Monkey *Mk = NULL; }; //声明子函数:实现生成链表,k为个数 Monkey *Create(int k); //子函数声明:链表输出; void Output(Monkey *Head); //将链表形成循环链表,即tail的指针指向开始位置; void Cricle(Monkey *&Head;,int n); //声明子函数实现循环链表例按照报数为m的出列剩下最后一个大王; void Check(Monkey *Head, int m); int main() { //声明头指针,作为链表的头; Monkey *head = NULL; int n = 8;//猴子个数 int m = 3;//规则数:报到3的猴子出列取消猴王权利; //①生成节点为8的链表; head = Create(n); //②输出结果检查; Output(head); //③将链表形成循环链表; Cricle(head, n); //Output(head);//检验是否形成了循环链表; //④猴王选举,并输出猴王; Check(head, m); return 0; } //编译生成链表子函数,这里采用*Ge,Ge和形参指向相同的地址,修改后面的有效; //另外Ge可以修改为别的地址,而不影响形参的指向。 Monkey *Create(int k) { //猴子数为零; if (k == 0) return NULL; //猴子数不为零; Monkey *p = NULL, *q = NULL; Monkey *Head = NULL;//用于储存待返回的表头; //先建立一个; p = new Monkey; p->Num = 1; Head = p; for (int i = 2; i Num = i; p->Mk = q; p = q; } return Head; } //子函数编译:输出链表结构; void Output(Monkey *Head) { int i = 1;//用于链表结点编号; while (Head != NULL) { cout << "猴子的编号是:" << Head->Num << endl; cout << "这只猴子的指针域值为" << Head->Mk <Mk; i++; } return; } //子函数编译:将链表形成循环链表,即tail的指针指向开始位置; void Cricle(Monkey *&Head;,int n) { Monkey *p = Head; for (int i = 1; i Mk; } //此时p指向第n个节点; p->Mk = Head; } //子函数编译:按照规则数选出猴王; void Check(Monkey *Head, int m) { Monkey *p = NULL, *q = NULL; q = Head; while (Head != Head->Mk)//检查是否只剩下一个猴子; { //找出规则数节点,删除节点; for (int i = 1; i Mk; } Head = q->Mk;//将头指针放到下一个要报数的猴子身上; p->Mk = q->Mk;//跳过了要删除的猴子; delete q; q = p->Mk; } //输出选举结果; cout << "猴王编号是:" << Head->Num << endl; }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值