2021-06-08

本文详细介绍了约瑟夫环问题的背景、需求和解决方案,使用循环单链表数据结构进行实现。通过示例展示了如何初始化、操作和销毁这个数据结构,并给出了实际的测试案例。代码实现包括了节点、循环单链表和解决约瑟夫环问题的类。最终,程序能够根据用户输入的人数、密码和报数上限,得出出列顺序。
摘要由CSDN通过智能技术生成

数据结构之练习——其一:约瑟夫环求解

 

系列前言:

近期在学习数据结构。数据结构这门课很有意思。课程内容听起来好像并没有很难理解,但是如果做课后习题就会发现习题没有那么容易。确实,学习数据结构不仅仅要认真听课堂上讲的,不仅仅要理解课本上的内容,还需要有相应的编程练习。

本人近期在做清华大学严蔚敏老师的习题集。其中的实习题挺有意思的,计划在这一系列博客中,写写我做的几道题以供交流。

 

问题——约瑟夫环求解

一、需求分析:

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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值