猴王问题(项目分析以及项目实现)

猴王问题:

        某森林中有n只猴子在商量猴王选举问题,所有的猴子都想当猴王,因此大家商量了一个选举办法如下:所有的猴子围成一圈,先从第一个猴子开始报数,报数到13的猴子就出列。紧接着的下一个猴子,又从1开始进行新一轮的报数,报数到12的猴子再出列;依此重复下去,每一轮报数都比上一轮的报数少1,直到报数减为1之后,又从13开始报数;直到原列中只剩下一个猴子为止,这个猴子就是猴王。试设计一个程序求出猴王。提示:用循环链表存储结构。

        那么,我们用项目的形式解决这个问题:
分析:

为了找出猴王,就需要不断的报数并且剔除,这看起来很像一个约瑟夫环的问题。

        所以思路很清晰,我们需要定义结构体来构成循环链表,之后书写约瑟夫环的函数实现剔除的功能。

第一步:书写结构体

        这个很简单,因为我们使用循环链表的方式,所以定义结点类型就OK,那么结点需要两个域,一个数据域,一个指针域。此处的typedef int Elemtype可以不写,因为这次只会用到int类型的数据。结构体里的struct Cnode* next就是指针域。

第二步:实现业务逻辑

        写一个大点的程序的时候,我们需要思考我们需要什么,比如这个猴王问题,我们就需要实现一个函数来找到那个被踢除的猴子。同时我们还需要实现一个循环链表来作为剔除的基础。所以针对这个项目,逻辑还是十分清晰的。

第三步:实现函数

        这一步是有一点点的难度的,主要就是定义两个函数:一个创建循环链表,一个剔除猴子(约瑟夫)

        那么,我们一起来实现一下这些函数吧

头文件monkeyking.h(书写声明和其它):

#pragma once
#define OVERFLOW -1
//定义结构体类型,用于创建循环链表
typedef int Elemtype;
typedef struct Cnode
{
	Elemtype data;
	struct Cnode* next;
}CNode;
//定义全局变量* monkeyjoseph
CNode* monkeyjoseph;
//创建循环链表
int Create_clist(CNode* clist, int n);
//约瑟夫环
int Joseph(CNode* clist, int m, int n, int k);

        头文件主要就是声明一类的代码,这里就不多解释了。

源文件monkeyking.c(书写定义):

#include"monkeyking.h"
#include<stdlib.h>
#include<conio.h>
//创建单循环链表
int Create_clist(CNode* clist, int n)
{
	CNode* p, * q;
	int i;
	clist = NULL;
	for (i = n; i >= 1; i--)
	{
		p = (CNode*)malloc(sizeof(CNode));
		if (p == NULL)
			return OVERFLOW;
		p->data = i;
		p->next = clist;
		clist = p;
		if (i == n)
			q = p;
	}
	q->next = clist;
	monkeyjoseph = clist;
	return 1;
}
//创建约瑟夫环
int Joseph(CNode* clist, int m, int n, int k)
{
	int i, temp, x = 0, king = 1;
	CNode* p, * q;
	temp = k;
	if (m > n)
		return -1;
	if (!Create_clist(clist, n))
		return -1;
	p = monkeyjoseph;
	for (i = 1; i < m; i++)
		p = p->next;
	while (p)
	{
		for (i = 1; i < k - 1; i++)
			p = p->next;
		q = p->next;
		printf("%-5d", q->data);
		x++;
		if (x == 15)
		{
			printf("\n");
			x = 0;
		}//每十五个数字换一行
		if (p->next == p) {
			king = p->data;
			p = NULL;
		}
		else {
			p->next = q->next;
			p = p->next;
			free(q);
		}
		if (k != 0)
			k--;
		else
			k = temp;
	}
	clist = NULL;
	return king;
}

首先来看创建循环链表:

        创建循环链表是整个题目得解的关键,只有正确的创建了循环链表,才能在约瑟夫环中实现正确的剔除。

        而创建链表就需要开辟空间并连接空间,链表的插入可以使用头插或者尾插,在这里我们使用头插。

        我们每次使用p来记录开辟的新结构体结点。使用clist来记录第一个结点(因为使用尾插,所以需要用q保存第一个结点的记录,以便最后一个结点指向第一个结点,如果使用头插,可以将第一个结点的位置在循环中用if条件保存下来)。每次开辟新节点,我们都需要让clist指向这个新开辟的结点,同时给这个新节点编号,并且注意,要让clist向后移动,也就是让clist成为新创建的结点,保证每次都是尾插。

        由于创建结点在循环中,所以循环结束后我们将首尾相连即可,此时我们就使用最后一个结点clist的next来指向第一个结点q,此时一个循环链表就创建完成了。

之后来看一下创建约瑟夫环

约瑟夫环的实现关键是链表数据的删除,那么我们就先说明一下链表的删除操作,我们在这一个操作上定义了p和q两个指针,q用来存储p移动到指定报数位置的数据,此时q就是我们需要的报数数据。所以我们先输出q的数据。

当我们输出了q的data数据之后,就需要判断p的下一个数据是不是自己,如果是自己,证明链表此时只剩下最后一个数据,也就是我们需要的猴王。如果p的下一个数据不是自己,那么我们就删除数据。这一步让p的next指向q的next就可以跳过做出报数的结点,跳过这个结点后,需要将结点销毁,那么我们就需要将其free掉,即free(q),此时就完成了删除的操作。

 

链表删除结点说明图

        在删除之后我们还要让报数递减(并且一旦报的数递减到0时,就需要重新赋值为开始的报数数据),所以我们书写一个if条件,如果k不等于0(k是报的数),那么就让k自减,如果k使用过后已经等于0了,就需要赋值。我们用temp来保存暂存的初始报数数据,此时再让k重新被赋值。就实现了报数的递减。

一:如何使报数依次递减1

报数减1,主要就是每次要在循环之后自减一下。

二:怎么输出猴王

        输出猴王很简单,我们可以在书写的约瑟夫环的函数里直接返回最后一个数据,也可以定义全局变量来记录最终的数据,也就是猴王。

到这里,我们的猴王问题的功能实现就解决了。

测试文件test.c(书写逻辑):

#include<stdio.h>
#include"monkeyking.h"
void main()
{
	int m, n, k;
	int king;
	CNode* clist;
	clist = NULL;
	k = 13;
	printf("请输入猴子数量n:");
	scanf_s("%d", &n);
	printf("请输入第一次开始报数的位置:");
	scanf_s("%d", &m);
	Create_clist(clist, n);
	printf("出列的顺序如下:\n");
	king = Joseph(clist, m, n, k);
	printf("\n所以猴王是最后一个出列的猴子:%d",king);
	printf("\n");
}

test文件也没什么可说的,就是逻辑的实现,看一下就能理解。

最终,整个项目也就实现了,希望本文章能够帮到你。

  • 25
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 20
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值