内容来源: VC知识库
某学校举行一场唱歌比赛,共有24个人参加,按参加顺序设置参赛号(参赛号为100至123)。
每个选手唱完一首歌之后,由10个评委分别打分。该选手的最终得分是去掉一个最高分和一个最低分,求得剩下的8个评分的平均分。
第二轮分为2个小组,每组6人,每个人分别按参赛号顺序演唱。当小组演唱完后,淘汰组内排名最后的三个选手,然后继续下一个小组的比赛。
第三轮只剩下6个人,本轮为决赛,不淘汰选手,本轮目的是赛出每个人的名次。该6人按参赛号顺序分别演唱。
一、总体分析所需要的结构体,类,类的外部接口,类的成员变量;
二、报名参加比赛的具体分析;
三、第一轮淘汰赛的分析;
四、第二轮淘汰赛的分析;
五、决赛的分析。
//在文件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: 函数对象,用于歌手分数的降序排列
为什么要采用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 ) );
}
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);
}
map<int, Singer> m_mapSinger
//打印参加比赛的歌手名字与参赛号
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);
}
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");
}
}
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);
}
}
list<int> m_lstRemainingID
multiset<int> _ mltsetScoreBeEliminatedInSecondRound
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