2021.12大二上学期数据结构结课设计
敢死队问题
问题描述: 有 M 个敢死队员要炸掉敌人的一碉堡,谁都不想去,排长决定用轮回数数的办法来决 定哪个战士去执行任务。如果前一个战士没完成任务,则要再派一个战士上去。现给每个战 士编一个号,大家围坐成一圈,随便从某一个战士开始计数,当数到 N 时,对应的战士就 去执行任务,且此战士不再参加下一轮计数。如果此战士没完成任务,再从下一个战士开始 数数,被数到第 N 时,此战士接着去执行任务。以此类推,直到任务完成为止。 排长是不愿意去的,假设排长为 1 号,请你设计一程序,求出从第几号战士开始计数 才能让排长最后一个留下来而不去执行任务。
基本要求: (1)输入队伍人数 M 和计数 N,输出排长指定的开始序号; (2)至少采用两种不同的数据结构的方法实现。
感谢朗哥献祭“松籁响起之时”给我们小组抽到了最简单的课程设计题目hhhhhhhhhhhhhhhhhhhhh
这题放到洛谷上也就普及+顶天了
以下为思路及优化
/*
* 感谢丁老师提供的扑克牌举例演示
* 需求分析:
* 输入:总人数m,报数要求n
* 输出:满足最后只剩排长一人的一次报数中第一个报数的人的编号
*
* 最后只剩排长一人即最后一个前去任务的人是排长
* 即排长前去任务后洞中无人,则该次报数为符合题意的报数
* 若一次派遣中,排长前去任务,此后洞中还有战士,则该次报数为不合题意的报数
* 名词注解:
* 一次派遣:指从1报到n,报到n的人出局。
* 一次报数:指从第一次派遣直到排长报数后前去任务
*
* 第一次会议记录:11.22上午9时。明确所使用两种数据结构为顺序表、链式队列。聂乐恒负责顺序表实现,杨朗负责链式结构队列实现。各自确认了数据结构解决问题过程
* 第二次会议记录:11.29上午9:30。就如何用队列解决问题产生意见分歧。yl思路为仅改变尾指针指向头指针位置,nlh思路为头指针和尾指针均正常向后移动(按照常规队列进行处理)以下为nlh思路部分代码
Queue que;//定义队列
a[i] = i+1;//a数组存的是队员编号
int x;
for(int i=0; i<m; ++i) {//一次报数中第一个报数的人,从0~m-1遍历
for(int j=0; j<m; ++j)//队员围成圈,从第一个报数的人开始向后延伸,队员入队
que.enqueue(a[(i+j)%n]);
do{//一次派遣
for(int k=0; k<n-1; ++k)//报数,报到1~n-1,不需前去任务
que.enqueue(que.dequeue());
x = que.dequeue();//报数,报到n,前去任务。x为前去任务的人的编号
}while(x!=0);//当排长前去任务时停止报数
if(que.empty())//排长前去任务后山洞内无人,是一次符合题意的报数
cout << i << "第一个报数" << endl;
while(!que.empty())//队列置空
que.dequeue();
}
*
* 第一次优化:Length()函数求山洞内剩余人数为遍历求得,可在类内部定义变量length,每次派遣--length
* 优势分析:支持每次派遣之间的n不同。实际意义在于,可能某次报数过程中,某战士为增大计算难度使每次派遣报数要求n不同。Get(x)支持x改变依然实现报数要求
*/
yl链式队列代码
点击此处查看代码LinkQueue.h
#pragma once
#include <iostream>
#define nullptr NULL
using namespace std;
const int MaxSize = 100;
template<typename DataType>
struct Node
{
DataType data = 0; //此战士的编号
bool completed = false; //默认每个战士都不能完成任务
Node<DataType>* next = nullptr;
};
template<typename DataType>
class LinkQueue
{
public:
LinkQueue(); //初始化空的链队列
~LinkQueue(); //释放链队列的存储空间
void EnQueue(DataType x, bool y); //入队操作,将元素x和y入队
DataType DeQueue(); //出队操作,将指定的对头元素出队
int Length(); //返回当前队列的长度
bool Empty(); //判断链队列是否为空
bool LeaderSurvival(); //判断链队列中是否只剩下队长
void Work(int M, int N); //求解问题
private:
Node<DataType>* front, * rear; //声明队头指针和队尾指针
int length = 0; //记录当前队列的长度
};
template<typename DataType>
LinkQueue<DataType>::LinkQueue() //函数功能:初始化空的链队列
{
Node<DataType> *s = nullptr;
s = new Node<DataType>;
s->next = nullptr;
front = rear = s; //将队头指针和队尾指针都指向头结点s
}
template<typename DataType>
LinkQueue<DataType>::~LinkQueue() //函数功能:释放链队列的存储空间
{
Node<DataType>* p = front;
while(!Empty()) //释放每一个结点的存储空间
{
front = front->next;
delete p;
p = front; //工作指针后移
}
}
template<typename DataType>
void LinkQueue<DataType>::EnQueue(DataType x, bool y) //函数功能:将元素x和y入队
{
Node<DataType>* s = nullptr;
s = new Node<DataType>; //申请结点s
s->data = x;
s->completed = y;
s->next = nullptr;
rear->next = s; //将结点s插入到队尾
rear = s;
++length;
}
template<typename DataType>
DataType LinkQueue<DataType>::DeQueue() //函数功能:将指定的队头元素出队
{
DataType x;
bool y;
if(Empty()) throw "队列已经为空。";
Node<DataType>* p = front->next; //暂存队头元素
x = p->data;
y = p->completed;
front->next = p->next; //将队头元素所在结点摘链
if(p->next == nullptr) rear = front; //出队前队列长度为1
delete p;
--length;
return x;
}
template<typename DataType>
int LinkQueue<DataType>::Length() //函数功能:返回当前队列的长度
{
//cout << "front->data:" << front->data << " ** " << "rear->data:" << rear->data << endl;
return length;
}
template<typename DataType>
bool LinkQueue<DataType>::Empty() //函数功能:判断队列是否为空
{
return (length == 0);
}
template<typename DataType>
bool LinkQueue<DataType>::LeaderSurvival() //函数功能:判断队列中是否只剩下队长
{
if ((length == 1) && (rear->data == 1))
return true;
return false;
}
template<typename DataType>
void LinkQueue<DataType>::Work(int M, int N) //函数功能:求解敢死队问题
{
int a[MaxSize], x;
for (int i = 0; i < M; i++) //讨论从0~10开始时,怎样才能使排长最后一个去执行任务
{
a[i] = i + 1;
for (int j = 0; j < M; j++) //将M个敢死队员入队
EnQueue(a[(i + j) % M], false);
do //数数并派遣队头的敢死队员去执行任务
{
//cout << "Testing:" << i << " ** Length:" << Queue.Length() << " ** ";
for (int j = 0; j < N - 1; j++)
EnQueue(DeQueue(), false);
x = DeQueue();
if (LeaderSurvival())
cout << "排长可以指定的开始序号为:" << i + 1 << endl;
} while (x != 1);
while (Empty() != true)
DeQueue();
}
}
nlh顺序表
点击此处查看代码SeqList.h
#pragma once
#include <iostream>
using namespace std;
const int Maxn = 101;
int a[Maxn];
class SeqList {
public:
SeqList() {//置零
m = n = id = 0;
for (int i = 0; i < Maxn; ++i)
data[i] = 0;
length = 0;
}
void Set(int M, int N) {
m = M, n = N, id = 1;
for (int i = 1; i <= m; ++i)
data[i] = a[i] = i;
length = m;
}
void Reset(int a[], int begin) {//重置顺序表内容,本次报数从begin开始
for (int i = 1; i <= m; ++i)
data[i] = a[i];
id = begin;
length = m;
}
int Length() {//返回目前队伍长度
return length;
}
int Get(int x) {//查找从id号队员开始报数目前第x位的值
int count = 0, i = id-1;//i=id-1是因为下面while中第一句++i
while (count != x) {
++i;
if (i > m) i = 1;
if (data[i] != 0)
++count;
}
// cout << i << ' ';//输出该次派出执行任务的战士编号
data[i] = 0;
--length;
id = i;
return i;
}
void Work() {//问题求解,输出合法编号
for (int i = 1; i <= m; ++i) {
Reset(a, i);//重置数组,本次报数第一个报数的人编号为i
while(Get(n) != 1);//执行派遣直到排长去执行任务
if(Length() == 0)//排长去执行任务后洞中无人
cout << "从第 " << i << " 号战士开始计数能让排长最后一个留下来" << endl;
}
}
private:
int data[Maxn];//data[i] = i,即编号对应编号
int m, n, id;//m为初始队伍人数,n为报数数字,id为下次开始报数的人的编号
int length;//剩余人数。*第一次优化
};
主文件
点击此处查看代码main.cpp
#include <iostream>
#include "SeqList.h"
#include "LinkQueue.h"
using namespace std;
int main(void) {
int m, n;
int op;
LinkQueue<int> Queue;
SeqList list;
do {
cout << endl << endl;
cout << "****************" << endl;
cout << "请输入指令" << endl;
cout << "0:停止程序" << endl;
cout << "1:链式队列解决问题" << endl;
cout << "2:顺序表解决问题" << endl;
cout << "****************" << endl << endl;
cin >> op;
if (op == 0) break;
IN: cout << "****************" << endl;
cout << "请输入队伍总人数m和报数要求n" << endl;
cout << "****************" << endl;
cin >> m >> n;
if (n < 0 || m < 0 || m > 101) {
cout << "输入数据有误!请重新输入" << endl;
goto IN;//重新输入
}
switch (op) {
case 1:
Queue.Work(m, n);
break;
case 2:
list.Set(m, n);
list.Work();
break;
case 0:
break;
default:
cout << "输入错误!请重新输入" << endl;
}
}while (op != 0);
return 0;
}