数据结构实验——约瑟夫环问题

实验要求:

从键盘输入人数N(N<50)及N个人的编号(整型)、姓名、性别和年龄,正向建立带头节点的循环链表;

输出循环链表各结点的值;

输入开始报数的人的编号S、间隔的个数M和剩余人数X;

在循环链表中查找到编号为S的结点;

从1开始向后报数,将报M的人(结点)从循环链表中删除,并输出该人的编号;

从刚才被删除人的下一个人开始重复步骤5,直至最后只剩下 X 个人为止;

输出最后剩余的人的编号、姓名、性别和年龄。

1、实验设计思路:

  很自然的想到用动态链表解决这一问题,根据题目要求需要建立带头节点的循环链表,结合头结点和循环链表的定义很容易实现这一功能。其次就是人这一类型的抽象,根据题目要求很容易设计出一个人的类型,其中包括人的编号姓名性别和年龄。这里还是采用头插法再通过一个逆向函数(ListInvert实现正向创建链表;

  尤瑟夫主干算法直接根据定义,从开始报数的人开始,每个一定的数目就将报到M的人从链表中删去再从下一个人开始继续这个过程。这里采用的是循环链表所以要非常注意报到M的时候可能是头指针L而不是人,如果把头指针L删除的话会产生非常严重的后果所以要特判边界情况。最后根据剩余人数设计循环,实现这一算法。

  除了链表、人的类型和算法主体,还需要设计打印函数和销毁函数来完成实验要求;

2、主要函数实现和代码说明 :

2.1人的类型
struct Person//人的类型	{
	string id;//唯一的id
	int age;//年龄
	bool sex;//性别(1为男性,0为女性)
	string name;//姓名
};
节点类型
struct LNode//节点(包括人和它的下一个节点的地址)
{
	Person data;
	LNode* next;
};
链表的相关函数(链表的创建、置逆、打印、求表长、删除元素、头插法和查找)
LinkList InitList()//建立一个循环链表
{
	LinkList L = new LNode;//在内存中为头节点开辟一段空间
	if (!L)exit(1);
	L->next = L;
	return L;//用返回的方式拿出这段内存
}
int LocateElem(LinkList L, string id)//由id找到pos
{
	int pos = 0;//元素编号从1开始
	LinkList p = L;
	while (p->data.id != id)
	{
		p = p->next; pos++;
	}
	return pos;
}
void DestroyList(LinkList L)//销毁一个链表
{
	LinkList p = L;
	while (p!=L)
	{
		LinkList s = p;
		p = p->next;
		delete s;
	}
	L = NULL;
}
void ListInsert(LinkList L)
{
	LinkList s = new LNode;
	if (!s)exit(1);
	cout << "************************" << endl;
	cout << "请依次输入添加人员的信息:" << endl;//给节点的信息赋值;
	cout << "编号为(编号两两之间不能相同):" << endl; cin >> s->data.id;
	cout << "姓名为:" << endl; cin >> s->data.name;//用malloc分配空间时给名字赋值时会报错,因为malloc不能提供string的构造函数;
	cout << "性别为(1为男0为女):" << endl; cin >> s->data.sex;
	cout << "年龄为:" << endl; cin >> s->data.age;
	cout << "添加成功!" << endl;
	system("pause");
	s->next = L->next;
	L->next = s;//头插法逆向建立链表;
	system("cls");
}
void ListInvert(LinkList L)//链表置逆
{
	LinkList p = L->next;
	L->next = L;//将头指针重置
	while (p != L)//从第一个元素开始依次头插法完成逆序转正序
	{
		LinkList s = new LNode;//将原链表的各个节点重新插入到置逆的链表中;
		s->data.id = p->data.id;
		s->data.name = p->data.name;
		s->data.sex = p->data.sex;
		s->data.age = p->data.age;
		s->next = L->next;
		L->next = s;
		p = p->next;
	}
}
void ListPrint(LinkList L)//打印链表
{
	LinkList p = L->next;//重置工作指针
	while (p != L)//遍历整个链表
	{
		cout <<"表中剩余所有节点的编号为:" << p->data.id << " " << "姓名为:" << p->data.name << " " << "性别为:";
		if ((p->data.sex) == 1)cout << "男";
		else cout << "女";
		cout << " " << "年龄为:" << p->data.age << endl;
		p = p->next;
	}
}
void ListDelete(LinkList L,int pos)//删除链表中第pos个节点(节点从1开始)
{
	LinkList p = L; int j = 0;
	while (j<pos-1)//找到第pos个节点的前一个;
	{
		p = p->next; j++;
	}
	LinkList q = p->next; p->next = q->next;
	cout << "被删除的人的编号为:" << q->data.id << endl;
	delete q;
	system("pause");
}
int Length(LinkList L)//求表长
{
	LinkList p = L->next;
	int count = 0;
	while (p != L) { count++; p = p->next; }
	return count;
}
LinkList LocateAdd(LinkList L, string S)//由用户编号找到对应节点的地址
{
	LinkList p = L->next;
	while (p != L&&p->data.id!=S)
	{
		p = p->next;
	}
	return p;
}
这里的LinkList为LNode的指针类型
尤瑟夫主体算法:
void Yusuf(LinkList L,string S,int M,int X)
{
	LinkList begin = LocateAdd(L,S);//开始报数人的地址;
	int count = Length(L);//记录总人数;
	int j = 1;
	while (count != X)//只要count不等于X就一直循环并删除报M的节点
	{
		int m = M-1;
		while (m--)
		{
			if (begin == L)begin = L -> next;//如果begin循环工作指针为头指针了就往后走一位
			begin = begin->next;
			if (begin == L)begin = L->next;//当然如果begin的next指到了L的话会出现严重的错误这时再移动一次即可
		}
		LinkList p = begin; begin = begin->next;//begin从下一个数据开始
		cout << "第" << j << "个被干掉的节点编号为" << p->data.id << endl; j++;
		ListDelete(L, LocateElem(L, p->data.id));//根据id找到元素并删除
		count--;//总人数减一直到剩余X个人为止
	}
	system("cls");
}

3、运行结果展示

3.1读入总人数再依次添加每个人的信息;

3.2依次添加完6个人后打印一开始的链表

3.3输入开始报数的位置、间隔的人数和最后剩余的人数

3.4循环删除节点且程序会自动给出每次删掉的节点

3.5尤瑟夫算法结束后会打印表中剩余的节点

还测试了4、8个人的从不同位置和间隔不同人的报数结果,和预期结果一致;

4、反思

1.C++和C语言最好不要混用,例如不要用malloc为包含string的类申请空间,不然会出现不好预测的错误;

2.边界条件要判断清楚,不然很容易导致头结点被删除的情况,或者是节点添加位置出现异常;

5、源代码

//从键盘中读入人数N以及N个人的编号(int)、姓名、性别和年龄,正向建立带头结点的循环链表;
//输出循环链表各节点的值;
//输入开始报数的人的编号S、间隔的人数M和剩余人数X;
//从1开始向后报数、将报M的人删除,并输出这个人的编号;
//从刚被删除的下一个人开始重复直至剩下X人为止;
//输出剩余的人的编号、姓名、性别和年龄;
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<string>
#include<cstring>
#include"Person.h"
#include"LNode.h"

using namespace std;
typedef LNode* LinkList;//将LNode的指针类型重命名为LinkList

LinkList InitList()//建立一个循环链表
{
	LinkList L = new LNode;//在内存中为头节点开辟一段空间
	if (!L)exit(1);
	L->next = L;
	return L;//用返回的方式拿出这段内存
}
int LocateElem(LinkList L, string id)//由id找到pos
{
	int pos = 0;//元素编号从1开始
	LinkList p = L;
	while (p->data.id != id)
	{
		p = p->next; pos++;
	}
	return pos;
}
void DestroyList(LinkList L)//销毁一个链表
{
	LinkList p = L;
	while (p!=L)
	{
		LinkList s = p;
		p = p->next;
		delete s;
	}
	L = NULL;
}
void ListInsert(LinkList L)
{
	LinkList s = new LNode;
	if (!s)exit(1);
	cout << "************************" << endl;
	cout << "请依次输入添加人员的信息:" << endl;//给节点的信息赋值;
	cout << "编号为(编号两两之间不能相同):" << endl; cin >> s->data.id;
	cout << "姓名为:" << endl; cin >> s->data.name;//用malloc分配空间时给名字赋值时会报错,因为malloc不能提供string的构造函数;
	cout << "性别为(1为男0为女):" << endl; cin >> s->data.sex;
	cout << "年龄为:" << endl; cin >> s->data.age;
	cout << "添加成功!" << endl;
	system("pause");
	s->next = L->next;
	L->next = s;//头插法逆向建立链表;
	system("cls");
}
void ListInvert(LinkList L)//链表置逆
{
	LinkList p = L->next;
	L->next = L;//将头指针重置
	while (p != L)//从第一个元素开始依次头插法完成逆序转正序
	{
		LinkList s = new LNode;//将原链表的各个节点重新插入到置逆的链表中;
		s->data.id = p->data.id;
		s->data.name = p->data.name;
		s->data.sex = p->data.sex;
		s->data.age = p->data.age;
		s->next = L->next;
		L->next = s;
		p = p->next;
	}
}
void ListPrint(LinkList L)//打印链表
{
	LinkList p = L->next;//重置工作指针
	while (p != L)//遍历整个链表
	{
		cout <<"表中剩余所有节点的编号为:" << p->data.id << " " << "姓名为:" << p->data.name << " " << "性别为:";
		if ((p->data.sex) == 1)cout << "男";
		else cout << "女";
		cout << " " << "年龄为:" << p->data.age << endl;
		p = p->next;
	}
}
void ListDelete(LinkList L,int pos)//删除链表中第pos个节点(节点从1开始)
{
	LinkList p = L; int j = 0;
	while (j<pos-1)//找到第pos个节点的前一个;
	{
		p = p->next; j++;
	}
	LinkList q = p->next; p->next = q->next;
	cout << "被删除的人的编号为:" << q->data.id << endl;
	delete q;
	system("pause");
}
int Length(LinkList L)//求表长
{
	LinkList p = L->next;
	int count = 0;
	while (p != L) { count++; p = p->next; }
	return count;
}
LinkList LocateAdd(LinkList L, string S)//由用户编号找到对应节点的地址
{
	LinkList p = L->next;
	while (p != L&&p->data.id!=S)
	{
		p = p->next;
	}
	return p;
}
void Yusuf(LinkList L,string S,int M,int X)
{
	LinkList begin = LocateAdd(L,S);//开始报数人的地址;
	int count = Length(L);//记录总人数;
	int j = 1;
	while (count != X)//只要count不等于X就一直循环并删除报M的节点
	{
		int m = M-1;
		while (m--)
		{
			if (begin == L)begin = L -> next;//如果begin循环工作指针为头指针了就往后走一位
			begin = begin->next;
			if (begin == L)begin = L->next;//当然如果begin的next指到了L的话会出现严重的错误这时再移动一次即可
		}
		LinkList p = begin; begin = begin->next;//begin从下一个数据开始
		cout << "第" << j << "个被干掉的节点编号为" << p->data.id << endl; j++;
		ListDelete(L, LocateElem(L, p->data.id));//根据id找到元素并删除
		count--;//总人数减一直到剩余X个人为止
	}
	system("cls");
}
int main()
{
	int N;
	cout << "请输入总人数:" << endl;
	cin >> N;
	LinkList L=InitList();//创建链表
	while (N--)ListInsert(L);
	ListInvert(L);//插入之后完成置逆操作
	ListPrint(L);//输出节点数据
	string S;int M, X;
	cout << "请输入开始报数的人的编号:" << endl; cin >> S; //S为开始报数的人的编号
	cout << "请输入间隔的人数:" << endl; cin >> M; //M为间隔的人数
	cout << "请输入剩余的人数:" << endl; cin >> X; //X为剩余的人数
	Yusuf(L, S, M, X);//执行尤瑟夫算法
	ListPrint(L);//打印剩余的元素
	system("pause");
	DestroyList(L);//释放空间
	return 0;
}

 

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值