一、实验目的
使用队列数据结构,模拟Josephus排队问题,根据输入的总人数和间隔数,求出最后一位幸存者,并在屏幕上可视化输出出来。
二、实验思路
采用链队列模拟排队,根据总人数初始化并创建链队。当在队里的总人数大于1个人时,遍历链队里所有节点。每访问一个就让它出队,如果它满足继续排队的条件,再让它进队插在队尾(即这个节点的序号不是循环间隔的倍数),反映到实际问题上就是这个人还可以存活。如此反复进行下去,直到链队里还剩下最后一个节点。
频繁的出队和入队是这种算法的缺点,但是由于队列“先进先出”的性质,无法在队列里查找待出队的元素,然后使其前驱指针指向其后继。所以如果这个实验不用队列,而用其他数据结构,比如操作不受限的链表,可能效率会更高。
因为频繁地出队和入队,所以不适合用顺序结构实现此实验。
三、链队定义
根据业务要求,链队中每个节点需要包括的成员为:int num,double x,double y,void *object,QNode *next. 其中,num是每个节点的序号,x,y是每个节点在屏幕上的位置,object是下文为实现动态绘制与消除所定义的一个void型指针变量,next是指向下一个节点的QNode型指针变量。
其中,nSize是Josephus问题的总人数,interval是间隔人数,这里将这两个从键盘输入的参数定义为链队的成员。front,rear分别是链队的头指针和尾指针,它们都是QNode型指针变量,且节点从front出队,从rear入队。
四、链队行为
①int InitQueue(LinkQueue *Q,int nSize,int interval);
从main函数里传入链队“宏观意义上的”指针以及相关参数,在Init()里对链队的相关属性进行设置。
②int DestroyQueue(LinkQueue *Q);
程序运行结束时要销毁这个链队,也就是要销毁链队里的每个节点,所以要对NULL值进行判断。如果非空,则遍历每个节点并销毁。最后销毁头结点,并赋值为NULL。
③int CreateQueue(LinkQueue *Q,double cx,double cy,double radius);
nSize是圆的顶点数,即链队中的节点数,也就是实际问题中的总人数。这里的nSize是作为链队的一个属性实现的,并非参数传递。
调用自定义DSGraphic库里的函数drawPoint(),drawText()绘制圆上的顶点和对应的序号。
④int EnQueue(LinkQueue *Q,double x,double y,int num);
链队的入队操作,这里新生成的入队节点的相关属性(x,y,num)是从函数外部传来的,要结合下面的函数看。
⑤int DeQueue(LinkQueue *Q,double *x,double *y,int *num);
链队的出队操作,因为Josephus问题的业务需要,对出队的节点,保存其相关信息(x,y,num),以便以后入队时为新节点赋值。
这里要注意,链队的出队操作,要防止误删尾指针。指的是如果要出队的是最后一个节点,那么要保存尾指针,使尾指针指向头结点。因为尾指针总是指向最后一个节点,如果直接就这样把最后一个节点删除了,尾指针也就丢失了。
⑥void DeadCircleLogic(LinkQueue *Q);
这个函数是整个算法的核心,count用于记录到找到最后一位幸存者一共进行的判断次数。num,x,y记录出队节点的信息,以便下次再显示时可以显示出这些信息。nSize是链队中的节点数,当入队时nSize加1,出队减1。当nSzie不等于1时,就一直执行循环体内的语句。
总的思想就是,对遍历到的每个节点都出队,如果它不是interval的倍数再让它入队。如此反复进行,直到找到最后一个“幸存者”。
这里clearObject(),Q->rear->object=drawPoint(…)的作用都是为了更好的显示效果,与自定义的DSGraphic库函数有关。
五、main函数
输入Josephus问题要求的人数nSize和间隔数interval,并对输入的正确性进行检查。setPlayingSpeed()是自定义的与绘制图形的速度有关的函数,与DSGraphic函数库有关。
定义链队变量Q并指向一个新节点。初始化这个链队,传入链队的“宏观上的”指针,为链队设置各项参数。根据输入的(400,200,150)(x,y,radius)信息,入队每一个节点,并计算每一个节点在屏幕上的坐标,最后把它们画出来。执行核心算法逻辑,对Josephus问题进行求解,最后销毁这个链队。