单循环赛中选手胜负序列求解问题.docx
- 文档编号:6075094
- 上传时间:2023-01-03
- 格式:DOCX
- 页数:21
- 大小:56.72KB
单循环赛中选手胜负序列求解问题.docx
《单循环赛中选手胜负序列求解问题.docx》由会员分享,可在线阅读,更多相关《单循环赛中选手胜负序列求解问题.docx(21页珍藏版)》请在冰豆网上搜索。
单循环赛中选手胜负序列求解问题
XX学院
计算机科学与技术系
课程设计报告
2008~2009学年第2学期
课程
数据结构与算法
课程设计名称
单循环赛中选手胜负序列求解问题
学生姓名
学号
专业班级
指导教师
2009年2月
题目:
单循环赛中选手胜负序列求解问题
一、问题分析和任务定义
单循环赛:
单循环赛,是所有参加比赛的队均能相遇一次,最后按各队在全部比赛中的积分、得失分率排列名次。
如果参赛球队不多,而且时间和场地都有保证,通常都采用这种竞赛方法。
单循环比赛轮次的计算
本题可有两种不同的理解,一个是按比赛的积分排名产生胜负序列,第二个是按比赛过程中各个选手间的胜负关系产生胜负序列,下面具体分析:
(1)按比赛中积分排名产生胜负序列:
比赛可规定各个选手之间均遭遇且只遭遇一次,比赛时胜方得1分,负方不得分,在比赛结束时按积分排名进行排序,由此产生胜负序列关系。
(2)按比赛过程中各个选手间的胜负关系产生胜负序列:
该种方法是以过程中的胜负为标准从而产生胜负序列,当然,这种胜负序列很大的可能性是不唯一的,本程序按课程设计任务书的要求,仅求出其中的一个胜负序列关系。
二、数据结构的选择和概要设计
(1)对于第一种情况,本实验选用的数据结构是类。
将选手的编号、积分、以及胜负处理等等封装在类中。
胜负序列的求解转化为了对所有选手的积分的排序问题。
(2)对于第二种情况,本实验采用的数据结构是有向图,每个选手视为一个点,每条边视为选手之间的胜负关系,箭头指向的一方为失败方。
所以胜负序列的求解就转化为了图的深度遍历问题。
为了便于深度遍历有向图,采用的存储结构为图的临街矩阵存储。
三、详细设计和编码
(1)就第一种情况而言,较为简单,设计一个选手类Player,私有成员包括分数score和选手编号num。
公有成员包括构造函数Player(),设置编号voidsetnum(intnum1),获胜处理函数voidwin(),失败处理函数voidfail(),返回编号函数intgetnum(),返回积分函数intgetscore()。
类的定义如下:
classplayer//选手类
{
private:
intscore;//积分
intnum;//参赛编号
public:
player()
{}
voidsetnum(intnum1)//设置编号
{
num=num1;
score=0;
}
voidwin()//胜利
{
score++;
}
voidfail()//失败
{
score--;
}
intgetscore()//获取分数
{
returnscore;
}
intgetnum()//获取编号
{
returnnum;
}
};
这些代码定义为头文件score.h
在程序的初始化时根据输入的选手数量n,动态申请一个选手类的数组Player[n]。
依次输入第一个选手和后面的n-1个选手的胜负关系,第二个选手和后面n-2个选手的胜负关系……第n-1个选手和第n个选手的胜负关系。
W(w)表示胜利,F(f)表示失败。
在选手的胜负关系输入完毕后通过getscore()返回各个选手的积分对各个选手的积分进行排序,从而得到选手的胜负序列关系。
(2)就第二种情况而言,较为复杂,使用的图,即将比赛过程中的各个选手间的胜负关系转化为一个有向图且是连同的完全有向图。
模型表示:
由于仅涉及到n个选手,并且这些选手之间的关系仅是胜负关系,因此可用图来表示,用顶点表示选手,用弧表示选手之间的胜负关系:
当且仅当Pi胜Pj时,有从顶点i到j的一条弧。
在这种表示下,本题问题变成了如下的问题:
在有向图中求解出一条包含所有顶点的简单路径的问题。
下图所示为一个有8个选手的问题的一个示例。
其中的一个解为1,2,3,4,8,6,5,7。
算法设计:
设计本题算法的构思如下:
为搜索出符合条件的简单路径,需按深度优先搜索方式进行遍历。
因此求解算法应是深度遍历算法的变形形式,也应是递归形式的算法。
由于要求遍历序列中的各结点按次序构成一条简单路径,因此算法与深度遍历算法有明显的不同:
并非任意选择的起点和访问次序都能得到解。
而这些又是事先难以确定的。
这就要求在求解过程中进行试探:
试探起点以及访问次序。
既然要在求解过程中进行试探,则需要记录试探的中间状态:
某顶点是否在当前试探的路径中,已经试探的各顶点的次序,当前正在试探的顶点等。
将所用到的变量及有关参数设置如下:
设置MAX_POINT_NUM100为最大选手数量。
路径结构WAY,包含路径上选手在序列数组中的小标k和选手序列way[MAX_POINT_NUM],都是整型。
设置结构体Graph为图的存储结构,包含选手的人数n和存储矩阵edges[MAX_POINT_NUM][MAX_POINT_NUM],为邻接存储矩阵,即本程序采用临街矩阵作为图的存储结构。
设图为g,设当前试探路径中最后的顶点号为i,i在试探路径中的序号为k,用整型数组visited[n]表示各顶点是否在当前试探的路径中(初值为全0),用路径w中的way[MAX_POINT_NUM]存储当前路径中的各顶点(在前k个元素中,事实上应是栈)。
既然是试探型求解,则需对当前顶点i的每个邻接点(不妨用j表示)进行试探,试探由i经j往下是否可以得到解。
每个i都可能有成功(指现在可以将该顶点放在路径上,这包括暂时的和最终的)与失败(指此路不通)两种情况,对此应分别作不同的处理:
a.若试探成功,则应将j放入路径中,并置相应的状态值。
然后再由j往下求解。
b.若不成功,则应恢复j的有关信息,以使j在试探其它路径时成为可选顶点。
为了能求出解以及所有可能的解,需要作如下两方面的工作:
a.选择起点:
应以每个顶点为起点进行搜索。
b.搜索路径:
在从i往下搜索时,应依次选择i的所有不在当前试探路径中的邻接点往下搜索。
为此,需要有这方面的保证:
应在试探某顶点j后并在换下一个试探顶点前恢复j的有关状态,以使其仍为可选择的顶点。
由上述讨论得本算法的基本思想:
a.若k=n-1,则说明已经求得一解,因此可输出结果,并结束本次调用。
b.若k 试探: 将j放到way[MAX_POINT_NUM]中,并置visited[j]为1,然后以j为起点往下搜索。 恢复: 将j恢复为不在当前路径中,以使其在试探其它路径时可用。 有关算法中的变量设置: 可将上述讨论中所提及的变量k、i作为参数(仅提供值而不返回值),将g作为地址传送型参数(虽然不会改变其值,但这种形式更节省时间和空间),将数组way和visited作为全程变量,以便各调用层能共享,并节省时间、空间,同时使程序更清晰。 算法描述: 程序开始要求输入选手的数量(即图中点的个数)。 并初始化way[MAX_POINT_NUM]全为-1,visited[j]为1,邻接矩阵edges[MAX_POINT_NUM][MAX_POINT_NUM]全为0。 提示依次输入第一个选手和后面的n-1个选手的胜负关系,第二个选手和后面n-2个选手的胜负关系……第n-1个选手和第n个选手的胜负关系。 W(w)表示胜利,F(f)表示失败。 初始化完成后尝试以不同的选手为起点进行搜索(从第1个到第n个)。 每次选择新的起点时对以上各个数组个数据从新初始化。 Graph*Creat_Graph(intn),为图的建立函数,根据选手的人数设置进行必要的初始化并且根据用户输入的胜负关系置相应的状态。 voidPush(intnum),为访问路径入栈,并且置相应的状态,即将编号为num的点入栈,也就是存储在w.way[MAX_POINT_NUM]中,并使w.k++;visited[num]=1。 voidPop(intnum,)出栈,并且置相应的访问状态,即将编号为num的点出栈,也就是存储在w.way[MAX_POINT_NUM]中,并使w.k--;visited[num]=0。 voidPrint_Graph(Graph*g,intn)为输出图的存储矩阵,以矩阵的形式输入。 intTest(Graph*g,inti,intn),试探当前结点是否可行,若可行,则入栈并返回1,否则出栈并返回0,把测试的结果返回给深度搜索函数voidSearch(Graph*g,inti,intn),Search根据返回值进行向的处理。 voidSearch(Graph*g,inti,intn),深度搜索函数,也就是本算法的核心函数,若n=w.k-1则退出。 根据参数i从第i行开始搜索edges[i][],遇到1的点edges[i][j]的时候对该点进行测试,调用Test(g,j,n),若返回1则递归调用Search(g,j,n)继续搜索;若返回0则跳过该点从下一个合适的点进行测试,并根据测试的结果进行和上面相同的处理方式。 voidInit(),初始化函数,对w.way[MAX_POINT_NUM]全部置-1,对visited[MAX_POINT_NUM]全部置0。 具体设计如下: #defineMAX_POINT_NUM100//最大选手数量 intvisited[MAX_POINT_NUM]={0};//访问标志数组,0=未访问,1=已经被访问 typedefstruct { intk,way[MAX_POINT_NUM];//k: 路径上的点点数量-1,way[]存储路径 }WAY; typedefstruct { intn;//图的顶点个数,即为选手个数 inte;//单循环赛,所以图的边数=选手个数 intedges[MAX_POINT_NUM][MAX_POINT_NUM];//邻接存储矩阵 }Graph; WAYw; Graph*Creat_Graph(intn)//图的建立 { inti,j; chartem; Graph*g=(Graph*)malloc(sizeof(Graph)); g->n=g->e=n; for(i=0;i { for(j=0;j g->edges[i][j]=0; } for(i=0;i { cout<<"请输入第"< for(j=i+1;j { cin>>tem; if(tem=='w'||tem=='W')//胜,则将胜负关系插入现有的邻接表中 g->edges[i][j]=1; elseif(tem=='f'||tem=='F')//负,存储相应的胜负关系 g->edges[j][i]=1; else { cout<<"错误输入,请重新输入其后的胜负关系"< j--; continue; } } } returng; } voidPush(intnum)//访问路径入栈,并且置相应的状态 { w.way[w.k]=num; w.k++; visited[num]=1; } voidPop(intnum)//出栈,并且置相应的访问状态 { w.k--; visited[num]=0; } voidPrint_Graph(Graph*g,intn)//输出图的存储矩阵 { inti,j; for(i=0;i { for(j=0;j cout< cout< } } intTest(Graph*g,inti,intn)//试探当前结点是否可行,若可行,则入栈,否则出栈 { intflag=0;//flag=0表示试探失败,1=试探成功 Push(i); for(intj=0;j if(g->edges[i][j]==1) flag=1; if(! flag) Pop(i); returnflag; } voidSearch(Graph*g,inti,intn)//深度搜索函数 { if(w.k==n-1)//已经搜索完毕 return; intj; for(j=0;j { if(g->edges[i][j]==1&&visited[j]==0)//找到合适的路径 { if(Test(g,j,n))//试探成功,则递归搜索 Search(g,j,n); else continue;//试探失败,则从i的下一个邻接点试探 } } } voidInit() { w.k=0; for(inti=0;i { w.way[i]=-1; visited[i]=0; } } voidProcess()//主函数 { intn; cout<<"请输入参赛人数: "; cin>>n; Graph*g=Creat_Graph(n); Print_Graph(g,n); cout<<"胜负序列如下: "< for(inti=0;i { Init(); Search(g,i,n); if(w.k==n-1) { for(intj=0;j<=w.k;j++) cout< cout< } } } 这些代码定义为头文件process.h (3)主函数main() 输出用户界面,提示用户根据提示进行选择,并对用户的错误输入进行处理和提示重新操作。 根据用户的正确操作调用相应的处理函数。 代码如下: voidmain() { charchoice; cout<<"\t\t单循环赛中选手胜负序列求解问题"< cout<<"\t\t请选择相应的胜负判定标准: "< cout<<"\t\tA(a): 以单循环赛中的积分排名为标准"< cout<<"\t\tB(b): 以单循环赛过程中的胜负为标准"< for(;;) { cout<<"你的选择="; cin>>choice; switch(choice) { case'a': Score(); break; case'b': Process(); break; default: { cout<<"输入有误,请重新输入! "< continue; } } break; } } 图3-1 四、上机调试 (1)第一种情况较为简单,在程序的设计和实现过程中没有任何问题。 一次通过编译且运行结果正确可行。 (2)第二种情况由于使用了复杂的图而且图的存储结构也较为复杂,还有就是使用了递归的深度搜索。 所以程序的设计并非一番顺利。 一下是程序调试过程中出现的问题: 1)在搜索的路径中出现了编号的重复,也就是说有些点在搜索成功后有被搜索了,显然这不符合本课程设计的要求,分析结果可知,次问题应该是由于在对结点的测试成功后并没有修改相应的标志数组中的相应位,导致了重复搜索。 在入栈函数Push()中加入了入栈功能,次问题得以解决。 同理在测试失败时也应该修改相应的标志位的状态位未被访问。 2)程序搜索完毕后发现路径上的结点个数大于参赛选手的人数,也就是说在上面的错误的基础上对程序递归的出口没有定义。 通过在Search()函数的开始出加入了相应的判断,若路径上的点数和参赛人数一致的时候推出递归。 问题得以解决。 3)对于某些胜负关系出现了无法求解的想象,表现位没有结果输出。 分析发现,程序默认的递归入口点为第一个结点,而这样由于可能出现第一个选手负于所有选手的情况,这样在程序深度递归搜索的第一个就卡住了,造成递归无法继续下去,通过对每个结点都作为入口点进行一次搜索并且在每次选择新的入口点时对访问标志数组和访问路径进行初始化的方法,次问题得以解决。 五、测试结果及其分析 1、如图5-1位出错处理。 在两种情况的求解中均有次处理。 图5-1 2、如图5-3为第一种情况的操作,即按积分的求解。 假定参赛人数位5人。 选手间的胜负关系入图5-2所示, 图5-2 图5-3 3、图5-4为第二种情况的求解过程。 其胜负序列关系也如图5-2,如图5-4,可以看出,对于选择不同的入口点,有了4中不同的求解序列,因为加入以第三个选手为入口点是无解的,因为第三个选手没有胜过一次。 程序也显示了图5-2的临街矩阵存储结构的直观图。 图5-4 六、用户使用说明 1、序的起始界面如图5-1所示,要求用户输入所有求解的方法,有两种,输入A或a选择第一种情况,输入B或b选择第二种情况,让输入了这4个字母外的其他字符的时候,程序报错,提示用户输入错误,并要求用户重新进行相应的选择。 2、如果输入A或a的时候进入第一种情况的处理界面,按提示用户依次输入第一个选手和后面的n-1个选手的胜负关系,第二个选手和后面n-2个选手的胜负关系……第n-1个选手和第n个选手的胜负关系。 W(w)表示胜利,F(f)表示失败。 让用户输入错误的时候报错并提示用户输入气候的相应胜负关系。 3、如果输入B或b的时候进入第二种情况的处理界面,使用方法同上。 七、参考文献 [1]王昆仑,李红。 数据结构与算法。 中国铁道出版社,2007。 [2]黄国兴,章炯民。 数据结构与算法。 清华大学出版社,2004。 [3]严蔚敏,吴伟民。 数据结构: C语言版。 北京: 清华大学出版社,2002。 [4]唐策善,黄刘生。 数据结构: 用C语言描述。 高等教育出版社,1999。 八、总结 本题主要部分是直接用C语言实现的一个设计,由于采用邻接矩阵作为图的存储结构,因此程序较为简洁。 若完全借助于实验工具,程序将更为简洁。 然而,邻接矩阵作为图的存储结构的要求在程序运行之前必须要知道其顶点数。 这是本设计的一个缺陷。 解决的办法之一是将邻接矩阵设置得“足够大”,运行时输入顶点数,将实际图的邻接关系存储在矩阵的左上角部分,然后依此运行。 另一解决的办法是采用邻接表作为存储结构,并将邻接表的表头指针也放到一个链表的各结点中,但随之而来的是程序设计量的加大,因为邻接表和“表头表”的建立、插入、查询、邻接点的查询等一系列功能的设计是较为麻烦的。 九、附录 主程序文件: main_final.cpp #include"iostream.h" #include"stdlib.h" #include"iomanip.h" #include"score.h" #include"process.h" voidmain() { charchoice; cout<<"\t\t单循环赛中选手胜负序列求解问题"< cout<<"\t\t请选择相应的胜负判定标准: "< cout<<"\t\tA(a): 以单循环赛中的积分排名为标准"< cout<<"\t\tB(b): 以单循环赛过程中的胜负为标准"< for(;;) { cout<<"你的选择="; cin>>choice; switch(choice) { case'a': Score(); break; case'b': Process(); break; default: { cout<<"输入有误,请重新输入! "< continue; } } break; } } 过程处理头文件: process.h #defineMAX_POINT_NUM100//最大选手数量 intvisited[MAX_POINT_NUM]={0};//访问标志数组,0=未访问,1=已经被访问 typedefstruct { intk,way[MAX_POINT_NUM];//k: 路径上的点点数量-1,way[]存储路径 }WAY; typedefstruct { intn;//图的顶点个数,即为选手个数 inte;//单循环赛,所以图的边数=选手个数 intedges[MAX_POINT_NUM][MAX_POINT_NUM];//邻接存储矩阵 }Graph; WAYw; Graph*Creat_Graph(intn)//图的建立 { inti,j; chartem; Graph*g=(Graph*)malloc(sizeof(Graph)); g->n=g->e=n; for(i=0;i { for(j=0;j g->edges[i][j]=0; } for(i=0;i { cout<<"请输入第"< for(j=i+1;j { cin>>tem; if(tem=='w'||tem=='W')//胜,则将胜负关系插入现有的邻接表中 g->edges[i][j]=1; elseif(tem=='f'||tem=='F')//负,存储相应的胜负关系 g->edges[j][i]=1; else { cout<<"错误输入,请重新输入其后的胜负关系"< j--; continue; } } } returng; } void
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 单循环赛 中选 胜负 序列 求解 问题
![提示](https://static.bdocx.com/images/bang_tan.gif)