数据结构之练习——其一:约瑟夫环求解
系列前言:
近期在学习数据结构。数据结构这门课很有意思。课程内容听起来好像并没有很难理解,但是如果做课后习题就会发现习题没有那么容易。确实,学习数据结构不仅仅要认真听课堂上讲的,不仅仅要理解课本上的内容,还需要有相应的编程练习。
本人近期在做清华大学严蔚敏老师的习题集。其中的实习题挺有意思的,计划在这一系列博客中,写写我做的几道题以供交流。
问题——约瑟夫环求解
一、需求分析:
1、本演示程序求解约瑟夫(Joseph)问题。每个编号对应的人所持有的密码是正整数,初始报数上限值也是一个正整数。
2、本演示程序以提示的方式,提醒用户输入总人数,按编号输入对应的密码,报数上限。程序运行的结果是依次出列的人员对应的编号序列。
3、本演示程序实现的功能是求解约瑟夫问题。问题的描述为:编号为1、2……n的人顺时针围成一圈,每个人持有一个密码。开始时,从编号为1的人顺时针报数,当报到一个给定的报数上限值m的时候,停止报数。报m的人出列,并将他的密码作为新的m值。刚出列的人的下一个人从1开始继续报数,类似上面过程,报到m的人出列,并更新m值为出列人的密码。重复这样的过程直到所有人都出列。求解出列顺序。
4、测试数据:n=7,密码依次为:3172484。m=20,正确的出列顺序为:6147235。
为实现上述功能,应该用循环单链表来表示n个人顺时针围成一圈。为此需要定义两个抽象数据类型。一个是循环单链表,一个是n个人围成的圈。
二、代码实现:
//本头文件定义循环单链表RoundList所需结点类型与指针类型
//函数类型定义在main文件中
typedef struct ElemType{ //元素类型
int num, code; //每个人的编号和密码
}ElemType;
typedef struct NodeType{
ElemType data;
NodeType *next;
}NodeType, *LinkType; //结点类型,指针类型
Status MakeNode(LinkType &p, ElemType e){
//分配由p指针所指向,元素数据为e,后继为空的结点,返回TRUE
//分配失败,返回FALSE
p=(LinkType)malloc(sizeof(NodeType));
if(!p) return FALSE;
p->data=e;
p->next=NULL;
return TRUE;
}//MakeNode
Status FreeNode(LinkType &p){
//释放p指向的结点
free(p);
return OK;
}//FreeNode
//本头文件实现抽象数据类型RoundList
//元素ElemType类型定义在Node.h头文件中
//结点Node及指针LinkType类型定义在Node.h头文件中
//函数类型定义在main文件中
#include "Node.h"
typedef struct RoundList{
LinkType head, tail; //头指针指向头结点,尾指针指向尾结点
int Length; //当前循环单链表长度
}RoundList; //循环单链表类型
Status InitRoundList(RoundList &L){
//创建循环单链表L,头指针尾指针都指向头结点,表长设为0,数据域为0
ElemType e;
LinkType p;
e.num=e.code=0;
if(MakeNode(p, e)) { //新建p指向的头结点
L.head=p; L.tail=p; L.Length=0; //将循环单链表L的头指针和尾指针指向分配的结点,表长设为0
L.head->next=L.head; //头结点后继指向自身
return OK;
}
else {L.head=NULL; return ERROR;}
}//InitRoundList
Status RoundListEmpty(RoundList L){
//实现循环单链表判空
//若L为空,返回TRUE,否则返回FALSE
if(L.Length==0) return TRUE;
else return FALSE;
}//RoundListEmpty
Status InsertAfterTail(RoundList &L, ElemType e){
//在循环单链表L最后一个结点后插入元素为e的新结点
//完成后返回OK
LinkType p;
if(!MakeNode(p,e)) return ERROR; //结点分配失败
p->next=L.tail->next;
L.tail->next=p;
L.tail=L.tail->next; //更新尾结点
++L.Length; //更新长度
return OK;
}//InsertAfterTail
Status DeleteAfter(RoundList &L, LinkType pre_pos, ElemType &e){
//删除L中pre_pos指向的元素的后继结点,以e返回删除元素的数据。表长减一
//操作完成之后返回OK
LinkType p;
if(L.Length==0) return ERROR; //表空不可删除
e=pre_pos->next->data;
p=pre_pos->next; //记录下待删除结点位置
pre_pos->next=pre_pos->next->next;
FreeNode(p);
--L.Length;
return OK;
}//DeleteAfter
Status DestroyRoundList(RoundList &L){
//销毁循环单链表L
//完成后返回OK
ElemType e;
while(L.head->next!=L.head)
DeleteAfter(L, L.head, e); //依次释放头结点后的第一个结点
FreeNode(L.head); //释放头结点
return OK;
}//DestroyRoundList
Status RoundListCount(RoundList L, LinkType initpos, LinkType &pre_stoppos, int m){
//initpos为循环单链表L中某个元素位置
//从initpos开始,从1计数直至m。用pre_stoppos返回第m-1个元素的位置
//完成后返回OK
LinkType p;
p=initpos;
for(int i=2; i<=m-1; ++i){
p=p->next; //依次报数
if(p==L.head) p=p->next; //跳过表头结点
}
pre_stoppos=p; //记录报m-1的人的位置
return OK;
}//RoundListCount
//本文件实现Joseph问题中的圈Circle数据类型
//元素ElemType,结点Node和指针LinkType的定义在Node.h文件中
//RoundList抽象数据类型定义在RoundList.h文件中
#include "RoundList.h"
typedef RoundList Circle; //RoundList新名
Status InitCircle(Circle &C, int &n){
//构造有n个人的圈
//提示用户依次输入人员数量,人员密码,初始化圈C
//操作完成后返回OK
ElemType e;
if(!InitRoundList(C)) return ERROR; //构造空圈C失败
cout<<"Please input total number of persons:"<<endl;
cin>>n;
for(int i=1; i<=n; ++i){
//依次输入各人的密码
cout<<"Please input the code of person "<<i<<":"<<endl;
cin>>e.code;
e.num=i;
InsertAfterTail(C, e);; //人员入圈
}
return OK;
}//InitCircle
Status DestroyCircle(Circle &C){
//销毁圈C
//完成后返回OK
DestroyRoundList(C);
return OK;
}//DestroyCircle
Status CircleEmpty(Circle C){
//Circle判空
//圈内无人返回TRUE,否则返回FALSE
if(RoundListEmpty(C)) return TRUE;
else return FALSE;
}//CircleEmpty
Status CircleCount(Circle C, LinkType initpos, LinkType &pre_stoppos, int m){
//圈C还有人
//从initpos开始从1依次报数,有人报m时停止报数
//以pre_stoppos返回报m-1的人的位置(便于报m的人出列)
RoundListCount(C, initpos, pre_stoppos, m);
return OK;
}//CircleCount
Status Dequeue(Circle &C, LinkType &initpos, LinkType pre_stoppos,int &m, int &num){
//报m的人出列。圈长减一,以num返回出列人的编号,更新m数值和下此报数起始位置initpos
//pre_stoppos为报m-1者的位置
ElemType e;
if(pre_stoppos->next==C.head) pre_stoppos=pre_stoppos->next; //跳过头结点
initpos=pre_stoppos->next->next; //更新报数起始位置
if(initpos==C.head) initpos=initpos->next; //跳过头结点
DeleteAfter(C, pre_stoppos, e); //删除出列人结点
num=e.num; //记录编号
m=e.code; //更新m值
return OK;
}//Dequeue
//本文件为求解Joseph问题的主函数文件
//ElemType,Node,LinkType的定义在Node.h头文件中
//循环单链表RoundList的定义在RoundList.h头文件中
//圈Circle的定义在Circle.h头文件中
//主函数直接调用模块为Circle模块
//Node、RoundList和Circle使用的库函数头文件仅被包含在本文件中
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <process.h>
#include <conio.h>
using namespace std;
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASTBLE -1
#define OVERFLOW -2
typedef int Status;
#include "Circle.h"
//主函数中函数
Status Initialization(Circle &C, int &m, int &n, LinkType &initpos, LinkType &pre_stoppos){
//系统初始化
//构建n人圈,初始化报数上限m
//初始化报数起始位置initpos和报数终止前一人位置pre_stoppos
//初始化出列编号存储用数组
//完成后返回OK
InitCircle(C,n); //构造n人圈
cout<<"Please input count limitation m:"<<endl;
cin>>m; //输入报数上限
initpos=C.head->next; //初始化报数起始位置,为第一个人
pre_stoppos=C.tail; //初始化报数终止前一人位置,为最后一个人
return OK;
}//Initialization
Status JosephSolution(Circle &C, int m, LinkType &initpos, LinkType &pre_stoppos, int n){
//求解Joseph问题,完成后返回OK
//返回出列编号顺序
int num; //记录出列人编号用变量
int i=1;
int num_array[n]; //初始化出列编号存储用数组
while(!CircleEmpty(C)){ //圈内有人
CircleCount(C, initpos, pre_stoppos,m); //报数
Dequeue(C, initpos, pre_stoppos, m, num); //出列,出列人编号为num
num_array[i-1]=num; //记录出列人编号
++i;
}
cout<<"The list of Dequeue:"<<endl;
for(int j=1; j<=n; ++j)
cout<<num_array[j-1]<<" "; //打印出列顺序
return OK;
}//JosephSolution
int main(){
//主函数
int n; //圈人数
int m; //报数上限
LinkType initpos, pre_stoppos; //报数开始位置,报m-1人的位置
Circle C;
Initialization(C,m,n,initpos,pre_stoppos); //初始化
JosephSolution(C,m,initpos,pre_stoppos,n); //求解Josph问题
DestroyCircle(C); //释放存储空间
return 0;
}//main
三、用户手册:
1、本程序的运行环境为DOS系统,执行文件为:JosephSolution.exe。
2、进入演示程序后按照提示依次输入Joseph问题参数即可。
四、测试结果:
输入人数7,依次输入每个人密码:3172484,输入初始报数上限20。问题求解结果:6 1 4 7 2 5 3。
输入人数7,依次输入每个人密码:3172484,输入初始报数上限15。问题求解结果:1 4 6 5 7 3 2。