猴子选大王

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

一、猴子选大王课题描述

一堆猴子都有编号,编号是1,2,3 ...m ,这群猴子(m个)按照1

到m的顺序围坐一圈,从第1开始数,每数到第n个,该猴子就要离开此圈,这样依次下来,直到圈中只剩下最后一只猴子,则该猴子为大王。

猴子选大王系统设计

1、猴子选大王总体设计结构图

2、系统数据的数据结构设计

猴子的存放采用链式存储结构,利用循环链表来实现建立的,其表示方法是递归定义的.

(1)、变量说明

程序中使用的存储结构:ListNode结构体

Struct ListNode{

Int data; //数据域

Struct ListNode *nextPtr; //指向后继结点的指针域

};

Int monkeys,count //分别为猴子的个数和猴子的报数

HeadPtr,tailPtr,currentPtr //分别为链表的头结点,尾结点和结点

HeadPtr1,headPtr2 //分别为选大王的单循环链表和由淘汰的猴子所构成的链表

(2)、函数说明

DestroyList (LISTNODEPTR headPtr) //用destroylist函数来释放选大王链表的各个结点

CreateList (int n) //创建循环链表,容纳n个猴子

Printf(“input the amount of monkeys:”) //用printf函数来提示输入的内容

Scanf(“%d”,&monkeys) //用scanf函数来输入猴子的个数

Printf(“input the count number:”) //用printf函数来提示报数的数

Scanf(“%d”,&count) //用scanf函数来输入每次数到的猴子就出局(3)、while循环说明

while(currentPtr1!=currentPtr1->nextPtr){

/*往后数一个猴子*/

prePtr1=currentPtr1;

currentPtr1=currentPtr1->nextPtr;

count++;

/*若数到n,则淘汰currentPtr指向的猴子*/

if(count%n==0){

/*从headPtr1指向链表中拆下currentPtr指向的结点*/

prePtr1->nextPtr=currentPtr1->nextPtr;

currentPtr1->nextPtr=NULL;

/*将currentPtr1指向的结点插入到headPtr2指向链表中*/

if(headPtr2==NULL){/*若headPtr2指向的为空链表*/

headPtr2=currentPtr1;

tailPtr2=currentPtr1;

}

else{ /*将拆下来的结点组装到headPtr2指向的链表上*/ tailPtr2->nextPtr=currentPtr1;

tailPtr2=tailPtr2->nextPtr;

}

currentPtr1=prePtr1; /*currentPtr1指向上一个结点,为下一次数数做准备*/

}

}

二、猴子选大王重点及关键技术分析

程序通过循环链表较好的实现了猴子选大王的功能,但是还是有许多值得思考的地方。

1、由于N = 1的情况比较复杂,程序中对它作了模糊处理,没有复杂化,直接用n>=2。

2、无论是用链表还是用数组实现都有一个共同点:要模拟整个过程,不仅程序比较烦,而且时间复杂度高达O(n*m),当n,m非常大的时候,几乎是没有办法在短时间算内出结果的。后来注意到原问题仅仅是要求出最后的猴子大王的序号,而不是要模拟整个过程。为了讨论方便,先把问题稍微改变一下,并不影响原意:

问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。

我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):

k k+1 k+2 ... n-2, n-1, 0, 1, 2, ... k-2

并且从k开始报0。

现在把他们的编号做一下转换:

k --> 0

k+1 --> 1

k+2 --> 2

...

...

k-2 --> n-2

k-1 --> n-1

变换后就完完全全成为了(n-1)个人报数的子问题,假如知道这个子问题的解:例如x是最终的猴王,那么根据上面这个表把这个x变回到x'=(x+k)%n

不刚好就是n个人情况的解吗!

如何知道(n-1)个人报数的问题的解?只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况---- 这显然就是一个倒推问题!到这里,思路出来了,下面就开始写递推公式:

令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n] 递推公式

f[1]=0;

f[i]=(f[i-1]+m)%i; (i>1)

有了这个公式,所要做的就是从1-n顺序算出f[i]的数值,最后结果是f[n]。因为实际生活中编号总是从1开始,我们输出f[n]+1

由于是逐级递推,不需要保存每个f[i],程序也是异常简单:

#include “stdio.h”

V oid main()

{

int n, m, i, s=0;

printf ("n和m的值是:\n ");

scanf("%d%d", &n, &m);

for (i=2; i<=n; i++) s=(s+m)%i;

printf ("猴王是%d\n", s+1);

}

这个算法的时间复杂度为O(n),相对于模拟算法已经有了很大的提高。可见,适当地运用数学策略,不仅可以让编程变得简单,而且往往会成倍地提高算法执行效率。

但是我总觉得这样做,太简单了,然后我就想到用链表来存放猴子数,并且做好是能把淘汰的猴子的序列显示出来,然后就想到了用两个链表来完成,一个链表用来表示选大王的循环链表,另一个用来表示淘汰的猴子所组成的表。

在怎个设计的过程中,使用到的有”数据结构中的遍历”、“创建”、“删除、“释放”以及指针和链表的一些操作,这些都是有一些难度的.

三、猴子选大王设计结构与分析

在修改程序的时候,出现了很多很多的错误,有一些很基本的,也有一些是我不能解决的,但最后通过一些办法,一步步的调试,最后终于调试成功了。

1、运行成功后显示画面如下图所示

2、输入猴子数89后显示的画面如下图所示

相关文档
最新文档