STL综合题:歌唱比赛

内容来源: VC知识库

题目:歌唱比赛

某学校举行一场唱歌比赛,共有24个人参加,按参加顺序设置参赛号(参赛号为100至123)。

每个选手唱完一首歌之后,由10个评委分别打分。该选手的最终得分是去掉一个最高分和一个最低分,求得剩下的8个评分的平均分。

第二轮分为2个小组,每组6人,每个人分别按参赛号顺序演唱。当小组演唱完后,淘汰组内排名最后的三个选手,然后继续下一个小组的比赛。

第三轮只剩下6个人,本轮为决赛,不淘汰选手,本轮目的是赛出每个人的名次。该6人按参赛号顺序分别演唱。

  • 请打印出所有选手的名字与参赛号,并以参赛号的升序排列。
  • 请打印出第1轮和第2轮淘汰赛中,各小组选手的名字与选手得分,并以名次的顺序排列。
  • 请打印出第1轮淘汰赛中被淘汰的歌手的名字(不要求打印顺序)。
  • 请打印出第2轮淘汰赛中被淘汰的歌手的分数,并以名次的降序排列。

【题目分析】

一、总体分析所需要的结构体,类,类的外部接口,类的成员变量;
二、报名参加比赛的具体分析;
三、第一轮淘汰赛的分析;
四、第二轮淘汰赛的分析;
五、决赛的分析。

1. 定义所需要的结构体,类,类的外部接口,类的成员变量

//在文件Singer.h中定义歌手的结构体
struct Singer
{
	string  strName;		//名字
	int  iLatestScore;		//最新得分
};
//类的声明在SingingCompetition.h中,类的实现在SingingCompetition.cpp中
//以下为对外开放的类成员方法(类的外部接口),我们主要要实现以下四个方法。
class CSingingCompetition
{
public:
	//报名参加比赛
	void JoinCompetition();

	//第一轮淘汰赛
	void FirstKnockout();

	//第二轮淘汰赛
	void SecondKnockout();

	//决赛
	void Finals();
};
map<int, Singer>  m_mapSinger;		//所有的参赛ID与歌手的映射集合。
//int:参赛ID,Singer:参加比赛的歌手。

为什么选择map? 而不选择其他容器呢?

答:每个歌手只想在内存中保存一份对象实例。另外我们希望能很快通过参赛ID来找到歌手和他的相关信息。从这里可以看出,我们选择关联性的容器是比较合适的。所以序列性的容器(Vector、List、Deque)都被排除了。关联性的容器有Set和Map两种。而Set是没有映射功能,而我们这边需要有映射关系的容器,所以只有Map类的容器才可以。而MultiMap的键值是可以重复的,但我们这边的ID是不能重复的,所以最后我们选择map容器来作为存放歌手的容器。

list<int> m_lstRemainingID;	//剩余歌手(没被淘汰的歌手)的参赛ID的集合。
//int:剩余歌手的参赛ID。

为什么要选用list,其它容器代替合适吗?

答:这个容器,是需要频繁地从容器的不确定位置删除元素的。List内部的 每个节点都是通过指针值的指向位置来相连接,在list中删除元素或插入元素,不会浪费或移动存储空间,只需要修改相应元素的指针指向值。vector或deque在中间或头部删除元素,将移动大量的空间,set与map是属于关联性容器,本身是排序的,而排序在这里是多余的,它们的内部实现比list复杂,删除的效率也没有list的删除效率高。

multimap<int, int, greater<int> >      m_mltmapCurGroup;
//当前演唱小组的歌手分数与歌手参赛ID的映射集合。
//第一个int: 歌手分数
//第二个int: 歌手参赛ID
//greater<int>: 函数对象,用于歌手分数的降序排列

为什么要采用multimap,选用其它容器合适吗?

答:我们要记录小组成员得分情况,而得分情况需要降序排序,我们就需要选一个容器,可以降序排列分数。这时我们可以选择set,multiset,map,multimap这些关联性容器,我们又需要从排序后的分数映射出分数的主人,也就是参赛ID,所以,我们排除了set,multiset,而歌手的分数是可能一样的,所以,排除了map,而选择了multimap。

vector<int> m_vecIDBeEliminatedInFirstRound;
//第一轮淘汰赛中被淘汰的歌手名字的集合。
//int: 歌手的参赛号

为什么选择vector,其它容器合适吗?

答:按题目的要求,这边的容器只要存储数据就行,没有删除元素的操作,没有排序的操作,选vector刚刚够用,且vector支持随机存储是最好的,数据也在内存中也是连续的。deque的第一个元素的位置不确定,随机存储性没vector好,而且deque的push_front与pop_back也是多余的,我们选择容器,够用就好。list不支持随机存储,要找到元素的下一个元素,时间久,且它提供的插入删除操作在这这是多余的。set的排序是多余的,不支持随机存储。map的排序也是多余的,映射也是多余的。

multiset<int>      m_mltsetScoreBeEliminatedInSecondRound;	//第二轮淘汰赛中被淘汰的歌手分数的集合。
//int: 歌手的分数

为什么选用multiset?其它容器合适吗?
答:由于这里需要排序,又不需要映射,所以,可以选set与multiset,不过分数是可能重复的,所以,选用multiset。

int m_iRound;  //第几轮比赛,值为1:第一轮;值为2:第二轮;值为3:第三轮。
CSingingCompetition::CSingingCompetition(void)
{
	//还没开始比赛,比赛轮数设置为0
	m_iRound = 0;

	//设置随机种子
	srand ( (unsigned)time ( 0 ) );
}

2. 具体条件的分析

  1. 生成24位歌手并让他们报名参加比赛
string strNameBaseSource("ABCDEFGHIJKLMNOPQRSTUVWXYZ");  //名字组成元素的来源

//随机排序名字组成元素的来源
random_shuffle(strNameBaseSource.begin(), strNameBaseSource.end());

for (int i=0; i<24; ++i)
{
		//获取参加比赛的歌手名字
		string strExt(1,strNameBaseSource[i]);

		//构造歌手对象
		Singer singer;
		singer.iLatestScore = 0;
		singer.strName = "选手";
		singer.strName += strExt;

		//录入参加比赛的歌手
		m_mapSinger.insert(pair<int, Singer>(i+100, singer));
		m_lstRemainingID.push_back(i+100);
}
  1. 报名参加比赛时的容器值
map<int, Singer>     m_mapSinger

在这里插入图片描述

在这里插入图片描述

  1. 打印歌手名字与参赛号
//打印参加比赛的歌手名字与参赛号
for (map<int,Singer>::iterator it=m_mapSinger.begin(); it!=m_mapSinger.end(); ++it)
{
	TRACE("%s,参赛号:%d\n", it->second.strName .c_str(), it->first);
}

在这里插入图片描述

3. 第一轮淘汰赛

void CSingingCompetition::FirstKnockout()
{
	if (m_iRound == 0)
	{
		m_iRound = 1;

		//进行淘汰赛
		Knockout();

		TRACE("第%d轮淘汰赛中被淘汰的歌手的名字:\n", m_iRound);
		for (vector<int>::iterator it=m_vecIDBeEliminatedInFirstRound.begin(); it!=m_vecIDBeEliminatedInFirstRound.end(); ++it)
		{
			TRACE("%s ", m_mapSinger[*it].strName.c_str());
		}
		TRACE("\n");
		TRACE("\n");
	}
}

// 淘汰赛规则
void CSingingCompetition::Knockout()
{
	TRACE("*************第%d轮淘汰赛:*************\n", m_iRound);

	int iSingerIndex = 0;		//第几个歌手正在演唱,1代表第一个歌手,2代表第二个歌手。。。
	for (list<int>::iterator it=m_lstRemainingID.begin(); it!=m_lstRemainingID.end(); )
	{
		++iSingerIndex;

		//生成歌手的分数
		MakeScore(m_mapSinger[*it]);

		//记录当前演唱小组歌手的得分情况,按分数降序排列
		m_mltmapCurGroup.insert(pair<int,int>(m_mapSinger[*it].iLatestScore, *it));

		if (iSingerIndex%6 == 0)
		{
			//小组演唱完毕,打印小组得分情况,淘汰删除歌手
			......
		}
		else
		{
			++it;
		}
	}
}
// 生成歌手分数
void CSingingCompetition::MakeScore(Singer &singer)
{
	deque<int> deqScore;		//为什么选用deque?  其它容器呢?
				//vector在去掉一个最高分,一个最低分时,需要从头部移除一个元素,效率低。
				//list不支持随机索引,在下面的accumulate的计算也比较慢。
				//set/multiset一开始就排序,按流程来说的十个评委评分过程是不排序的,然后评分后再排序去掉最高分最低分。如果不考虑流程,在这程序中可以使用multiset(分数可能一样,set不行),使一开始就排序好,不过在accumulate的计算中,也比较慢。
				//map/multimap是映射,在这边不需要。

	//十个评委分别对歌手打分,打分过程没要求排序
	for (int i=0; i<10; ++i)
	{
		int iScore = 60 + rand()%40;
		deqScore.push_back(iScore);
	}

	//为十个评委的打分排序
	sort(deqScore.begin(), deqScore.end());

	//去掉一个最高分,去掉一个最低分
	deqScore.pop_front();
	deqScore.pop_back();

	//求八个评委打分的总和
	int iScoreSum = accumulate(deqScore.begin(), deqScore.end(), 0);

	//求八个评委打分的平均分
	int iScoreAverage = (int)(iScoreSum/deqScore.size());

	//给歌手设置得分
	singer.iLatestScore = iScoreAverage;
}
//淘汰赛规则
void CSingingCompetition::Knockout()
{
	TRACE("*************第%d轮淘汰赛:*************\n", m_iRound);

	int iSingerIndex = 0;		//第几个歌手正在演唱,1代表第一个歌手,2代表第二个歌手。。。
	for (list<int>::iterator it=m_lstRemainingID.begin(); it!=m_lstRemainingID.end(); )
	{
		++iSingerIndex;

		//生成歌手的分数
		MakeScore(m_mapSinger[*it]);

		//记录当前演唱小组歌手的得分情况,按分数降序排列
		m_mltmapCurGroup.insert(pair<int,int>(m_mapSinger[*it].iLatestScore, *it));

		if (iSingerIndex%6 == 0)
		{
			//小组演唱完毕,打印小组得分情况,淘汰删除歌手
			......
		}
		else
		{
			++it;
		}
	}
}

第一轮第一个小组得分记录

multimap<int, int, greater<int> >           m_mltmapCurGroup

在这里插入图片描述

//淘汰赛
void CSingingCompetition::Knockout()
{
	TRACE("*************第%d轮淘汰赛:*************\n", m_iRound);

	int iSingerIndex = 0;		//第几个歌手正在演唱,1代表第一个歌手,2代表第二个歌手。。。
	for (list<int>::iterator it=m_lstRemainingID.begin(); it!=m_lstRemainingID.end(); )
	{
		++iSingerIndex;

		//生成歌手的分数
		MakeScore(m_mapSinger[*it]);

		//记录当前演唱小组歌手的得分情况,按分数降序排列
		m_mltmapCurGroup.insert(pair<int,int>(m_mapSinger[*it].iLatestScore, *it));

		if (iSingerIndex%6 == 0)
		{
			//小组演唱完毕,打印小组得分情况
			PriteGroupScore();

			//在当前小组中淘汰歌手
			EraseInCurGroup();

			//在剩余歌手中删除歌手
			EraseInRemainingID(it++);    // 不可用++it代替,因为要转入自增之前的迭代器
		}
		else
		{
			++it;
		}
	}
}
//打印当前小组的分数
void CSingingCompetition::PrintGroupScore()
{
	TRACE("小组得分情况:\n");
	for (multimap<int,int, greater<int> >::iterator it=m_mltmapCurGroup.begin(); it!=m_mltmapCurGroup.end(); ++it)
	{
		TRACE("%s的得分:%d\n", m_mapSinger[it->second].strName.c_str() ,it->first);
	}
	TRACE("\n");
}

在这里插入图片描述

// 在当前小组中淘汰歌手
void CSingingCompetition::EraseInCurGroup()
{
	int iSingerLastIndexInGroup = 0;		//组内歌手的倒数索引
	while(iSingerLastIndexInGroup<3)
	{
		//获取当前演唱小组的最后一个元素的迭代器
		multimap<int,int,greater<int> >::iterator it=m_mltmapCurGroup.end();
		--it;

		++iSingerLastIndexInGroup;

		if (m_iRound == 1)
		{
			//记录第一轮淘汰赛中被淘汰的歌手的参赛号
			m_vecIDBeEliminatedInFirstRound.push_back(it->second);
		}
		else if (m_iRound == 2)
		{
			//记录第二轮淘汰赛中被淘汰的歌手的分数
			m_mltsetScoreBeEliminatedInSecondRound.insert(m_mapSinger[it->second].iLatestScore);
		}

		//从当前演唱小组的集合容器中删除最后一个元素
		m_mltmapCurGroup.erase(it);
	}
}
// 在剩余歌手中删除歌手
void CSingingCompetition::EraseInRemainingID(list<int>::iterator it)
{
	int iSingerReverseIndexInGroup = 0;		//逆向遍历的索引
	while(iSingerReverseIndexInGroup<6)
	{
		//查找逆向遍历迭代器所指的参赛ID所对应歌手的{分数,参赛ID}是否在当前演唱小组中
		multimap<int,int,greater<int> >::iterator itMltmapScoreToID = 
			find(m_mltmapCurGroup.begin(),m_mltmapCurGroup.end(), 
			multimap<int,int,greater<int> >::value_type(m_mapSinger[*it].iLatestScore, 
			*it));

		if (itMltmapScoreToID == m_mltmapCurGroup.end())
		{
			//没找到,从剩余歌手集合中删除该歌手的参赛号
			it = m_lstRemainingID.erase(it);
		}

		//逆向遍历的索引自增
		++iSingerReverseIndexInGroup;

		//防止对容器的begin()迭代器进行--操作。
		if (it != m_lstRemainingID.begin())
		{
			--it;
		}
	}

	//清除该组的比赛记录存储,以便下一组比赛记录的存储
	m_mltmapCurGroup.clear();
}
第一轮第一个小组比赛完后容器的变化

在这里插入图片描述

vector<int>           m_vecIDBeEliminatedInFirstRound

在这里插入图片描述

multimap<int, int, greater<int> >           m_mltmapCurGroup

在这里插入图片描述

list<int>        m_lstRemainingID
第一轮第二个小组比赛完后容器的变化

在这里插入图片描述

multimap<int, int, greater<int> >           m_mltmapCurGroup

在这里插入图片描述

vector<int>           m_vecIDBeEliminatedInFirstRound

在这里插入图片描述

list<int>        m_lstRemainingID
第一轮四组全部比赛完后容器的变化

在这里插入图片描述

list<int>        m_lstRemainingID

在这里插入图片描述

vector<int>           m_vecIDBeEliminatedInFirstRound
//第一轮淘汰赛
void CSingingCompetition::FirstKnockout()
{
	if (m_iRound == 0)
	{
		m_iRound = 1;

		//进行淘汰赛
		Knockout();

		TRACE("第%d轮淘汰赛中被淘汰的歌手的名字:\n", m_iRound);
		for (vector<int>::iterator it=m_vecIDBeEliminatedInFirstRound.begin(); it!=m_vecIDBeEliminatedInFirstRound.end(); ++it)
		{
			TRACE("%s ", m_mapSinger[*it].strName.c_str());
		}
		TRACE("\n");
		TRACE("\n");
	}
}

在这里插入图片描述

4. 第二轮淘汰赛

1. 第二轮比赛前

在这里插入图片描述

void CSingingCompetition::SecondKnockout()
{
	if (m_iRound == 1)
	{
		m_iRound = 2;

		//进行淘汰赛
		Knockout();		//逻辑与第一轮差不多,区别在下页

		TRACE("第%d轮淘汰赛中被淘汰的歌手的分数:\n", m_iRound);
		for (multiset<int>::iterator it=m_mltsetScoreBeEliminatedInSecondRound.begin(); it!=m_mltsetScoreBeEliminatedInSecondRound.end(); ++it)
		{
			TRACE("%d ", *it);
		}
		TRACE("\n");
		TRACE("\n");
	}
}
// 在当前小组中淘汰歌手
void CSingingCompetition::EraseInCurGroup()
{
	int iSingerLastIndexInGroup = 0;		//组内歌手的倒数索引
	while(iSingerLastIndexInGroup<3)
	{
		//获取当前演唱小组的最后一个元素的迭代器
		multimap<int,int,greater<int> >::iterator it=m_mltmapCurGroup.end();
		--it;

		++iSingerLastIndexInGroup;

		if (m_iRound == 1)
		{
			//记录第一轮淘汰赛中被淘汰的歌手的参赛号
			m_vecIDBeEliminatedInFirstRound.push_back(it->second);
		}
		else if (m_iRound == 2)
		{
			//记录第二轮淘汰赛中被淘汰的歌手的分数
			m_mltsetScoreBeEliminatedInSecondRound.insert(m_mapSinger[it->second].iLatestScore);
		}

		//从当前演唱小组的集合容器中删除最后一个元素
		m_mltmapCurGroup.erase(it);
	}
}
2. 第二轮两组比赛完后容器的变化

在这里插入图片描述

list<int>        m_lstRemainingID

在这里插入图片描述

multiset<int> _   mltsetScoreBeEliminatedInSecondRound

在这里插入图片描述

4. 决赛的分析

void CSingingCompetition::Finals()
{
	if (m_iRound == 2)
	{
		m_iRound = 3;

		//第三轮决赛
		for (list<int>::iterator it=m_lstRemainingID.begin(); it!=m_lstRemainingID.end(); ++it)
		{
			//生成歌手的分数
			MakeScore(m_mapSinger[*it]);

			//记录当前小组歌手的得分情况,按分数降序排列
			m_mltmapCurGroup.insert(pair<int,int>(m_mapSinger[*it].iLatestScore, *it));
		}

		//打印小组决赛情况
		TRACE("*************小组决赛情况:*************\n");
		for (multimap<int,int, greater<int> >::iterator it=m_mltmapCurGroup.begin(); it!=m_mltmapCurGroup.end(); ++it)
		{
			TRACE("%s的得分:%d\n", m_mapSinger[it->second].strName.c_str() ,it->first);
		}
		TRACE("\n");

		//清除所有的数据
		m_mapSinger.clear();
		m_lstRemainingID.clear();
		m_vecIDBeEliminatedInFirstRound.clear();
		m_mltsetScoreBeEliminatedInSecondRound.clear();
		m_mltmapCurGroup.clear();
		m_iRound = 0;
	}
}
第三轮比赛(决赛)后得分记录

在这里插入图片描述

multimap<int, int, greater<int> > m_mltmapCurGroup

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/a731062834/article/details/82976396