1.问题定义
赛事管理系统
2.问题分析
(1)参赛队伍管理
首先,根据参赛队的基本信息定义一个结构体.之后,要考虑应该用什么数据结构去存储数据的内容.因为队伍的编号,不是连续的,这种情况用数组去存储不能充分运用其随机存取的优点,所以我决定采用链表.
之后,要将team.txt文件中的内容读取进程序中,需要进行文件操作.将读取到的信息分别赋值到相应的变量上.
(2)基于二叉树的查找
因为该问题硬性要求构建二叉排序树,并通过它进行查找。所以先要构建一个结构体,用来放team值和左右孩子。之后就根据数据结构课上所学的算法,将链表中的数据依次存入节点中,并插入到二叉排序树中。因为之后还要计算查找成功时的平均查找长度ASL,所以可以在构建树时就计算总共的查找长度。之后,写出在二叉排序树中查找给定的队伍编号的算法。如果查找成功,则输出该队伍对应的基本信息,包括参赛作品名称、参赛学校、赛事类别、参赛者和指导老师等;同时输出平均查找长度ASL,计算表达式为ASL = count/(teams.size()-1),其中count表示总查找长度,teams.size()-1表示参赛队伍的总数。(因为teams链表的第一个元素存放的是数据项,而不是具体的团队信息。)
此外,我们还应注意,对于二叉排序树,如果插入数据已经排好序,那么会构成斜树。这时候构建二叉排序树,反而不能提高查找的效率,平均时间复杂度为O(n/2),这时候,要将其变为平衡二叉树。但是,链表中数据并不是有序的,所以可以采用二叉排序树。
(3)参赛团队查询
为实现按参赛学校查询参赛团队。因为,我是用链表创建的,因为链表并不支持随机访问,需要访问后面节点需要从表头进行遍历,所以有些排序实现起来较为麻烦。其中,插入排序显然是一个很好的选择,因为它可以很方便地将每个待排序的节点插入到已经排好序的链表中,并保持链表的有序性质。但是,它的时间复杂度比较大,为O(n^2)。此外,我之前在数据结构实验课上已经实现了链表的插入排序,不需要花额外时间debug了。另一个适合用于链表排序的算法是归并排序,归并排序使用分治法思想,通过对链表进行逐级拆分和合并来实现排序,而且相对于其他排序算法,其空间消耗较小,这恰好符合了链表连续空间难以修改的特点。而其它的排序算法,由于它们的操作都需要在链表中进行大量的遍历和交换操作,所以相对来说,它们的时间复杂度和空间复杂度可能更大,所以不太适合在链表中使用。因此,对于上述排序算法,归并算法算是链表排序的最佳选择。
但之后,我加深学习了c++中STL库的知识,发现用list.sort()实现更加方便快捷。STL中的sort()除了对普通的快速排序进行优化,它还结合了插入排序和堆排序。根据不同的数量级别以及不同情况,能自动选用合适的排序方法。当数据量较大时采用快速排序,分段递归。一旦分段后的数据量小于某个阀值,为避免递归调用带来过大的额外负荷,便会改用插入排序。而如果递归层次过深,有出现最坏情况的倾向,还会改用堆排序。所以说sort()是一个比较灵活的函数,它也会根据我们数据的需要进行排序,所以我就决定用它了。
(4)决赛叫号系统
该问题可以使用队列数据结构进行解决。因为各参赛室要在基本相同的时间结束比赛,所以要将它们进行平分,并根据按顺序叫号时参赛队的先后顺序出队比赛。
对于每个决赛室的队列,按照参赛队伍在队列中的位置进行循环叫号,并设置适当的间隔时间,如0.5秒。
在进行队列的循环和叫号时,需要记录当前正在进入赛场的参赛队信息。当有新的参赛队要进场时,只有当前正在进场的队伍离开后,才能进行下一个队伍的叫号进场。
总之,该算法通过使用队列数据结构,按照参赛队的编号进行顺序排序,模拟了不同决赛室中参赛队按顺序进入赛场的情形。核心思想是利用队列数据结构,记录进入赛场的顺序,做到了合理排队、有序进场。
(5)校园导航
该问题属于图论的内容且该图是无向图。对于图的存储结构而言,图中各个景点的存储结构有邻接表和邻接矩阵两种存储结构。而我选择用邻接矩阵的方式创建,因为数据结构实验上,曾要求用该方法创建。此外,通过随机访问的方式,获取两点距离更加方便。
求两点之间的最短路径问题,有多种经典算法可以解决最短路径问题,包括Dijkstra算法,Floyd-Warshell算法,Bellman-Ford算法,深度优先遍历和A*算法,因为不存在两地之间距离为负数(如果存在,就说明不断在两地穿行所需要走的路反而变少,这不符合常理),所以以上算法皆可使用。但是A*算法需要估价函数,所以不予考虑。因为Dijkstra算法在教材上有详细的介绍,所以我就采用它了。
3.概要设计
定义一个team的结构体
struct team
{
//参赛队编号
string id;
//参赛作品名称
string entry;
//参赛学校
string school;
//赛事类别
string category;
//参赛者
string participant;
//指导老师
string teacher;
};
首先,定义一个类.
class EventManagement
{
private:
list <team> teams;
public:
EventManagement();
void Insert() ;
void Delect();
void Change();
void Find1();
void Find2();
void Calling();
void Show();
void Guid();
void Show1();//所有队伍查询
};
定义构造函数
在 getline
函数的第一个参数中,需要传入一个输入流,即 ifstream
对象,使用 stringstream
对象可以将字符串拆分为多个子串,并以指定的分隔符作为分界符.
此外,我还通过以下操作.实现了将文档中读取的空格全部清除掉了.
auto is_space = [](char c) { return std::isspace(static_cast<unsigned char>(c)); };
line.erase(remove_if(line.begin(), line.end(), is_space), line.end());
最后,构造函数的实现如下所示
EventManagement::EventManagement()
{
ifstream file("team.txt");
if (!file)
{
cout << "打开文件失败" << endl;
}
string line;
while (getline(file, line))
{
team t;
auto is_space = [](char c) { return std::isspace(static_cast<unsigned char>(c)); };
line.erase(remove_if(line.begin(), line.end(), is_space), line.end());
stringstream ss(line);
getline(ss>>std::ws, t.id, '#');
getline(ss >> std::ws, t.entry, '#');
getline(ss >> std::ws, t.school, '#');
getline(ss >> std::ws, t.category, '#');
getline(ss >> std::ws, t.participant, '#');
getline(ss >> std::ws, t.teacher, '#');
teams.push_back(t);
}
for (list<team>::iterator it = ++teams.begin(); it != teams.end(); it++)
cout << "参赛队编号:" << it->id << "参赛作品名称:" << it->entry << "参赛学校:" << it->school << "赛事类别:" << it->category << "参赛者:" << it->participant << "指导老师:" << it->teacher << endl;
file.close();
// 结构体已存入list teams中,可以进行后续操作
}
展示信息
void EventManagement::Show()
{
cout << "----------------------------------------------------"<<endl;
cout << "1.增加参赛队伍信息" << endl;
cout << "2.删除参赛队伍信息" << endl;
cout << "3.修改参赛队伍信息" << endl;
cout << "4.根据参赛队编号查找参赛队伍信息" << endl;
cout << "5.根据参赛学校查找参赛队" << endl;
cout << "6.决赛叫号系统" << endl;
cout << "7.校园导游程序" << endl;
cout << "8.退出程序" << endl;
cout << "----------------------------------------------------"<<endl;
cout << "请输入你要进行的操作(输入序号):" << endl;
}
增加
//增加参赛队伍
void EventManagement:: Insert()
{
team i;
cout << "请输入添加队伍编号:"<<endl;
cin >> i.id;
cout << "请输入添加队伍的作品名称" << endl;
cin >> i.entry;
cout << "请输入添加队伍学校:" << endl;
cin >> i.school;
cout << "请输入添加队伍的赛事类别:" << endl;
cin >> i.category;
cout << "请输入添加参赛者:" << endl;
cin >> i.participant;
cout << "请输入添加队伍指导老师:" << endl;
cin >> i.teacher;
teams.push_back(i);
for (list<team>::iterator it = teams.begin(); it != teams.end(); it++)
cout << it->id << endl;
}
删除
//删除参赛队伍(根据队伍的编号),并返回删除队伍
void EventManagement:: Delect()
{
string i;
cout << "请输入队伍编号:"<<endl;
cin >> i;
for (list<team>::iterator it = ++teams.begin(); it != teams.end(); ++it)
{
cout << it->id;
if(it->id== i)
{
team x = *it;
teams.erase(it);
cout << "已删除"<<endl;
cout << "删除队伍信息为:"<<endl;
cout << "参赛队编号:" << x.id << '\t' << "参赛作品名称:" << x.entry << '\t' << "参赛学校:" << x.school << '\t' << "赛事类别:" << x.category << '\t' << "参赛者:" << x.participant << '\t' << "指导老师:" << x.teacher << endl;
return;
}
}
cout << "不存在此队伍" << endl;;
}
更改
void EventManagement::Change()
{
string i;
cout << "请输入队伍编号:" << endl;
cin >> i;
team x;
for (list<team>::iterator it = ++teams.begin(); it != teams.end(); ++it)
{
if (it->id==i)
{
x = *it;
cout << "请对参赛信息进行修改:";
cout << "参赛作品名称:" << x.entry << '\t' << "参赛学校:" << x.school << '\t' << "赛事类别:" << x.category << '\t' << "参赛者:" << x.participant << '\t' << "指导老师:" << x.teacher << endl;;
return;
}
}
cout << "不存在此队伍" << endl;;
}
首先,创建二叉排序树的节点。存取团队信息与左右孩子。
之后通过迭代器遍历,将链表中的数据赋值给节点。因为teams链表的第一个元素存放的是数据项,而不是具体的团队信息。所以从++teams.begin()开始遍历。
//基于二叉排序树进行查找,输入参赛队编号
struct TreeNode
{
team data;
TreeNode* left = NULL;
TreeNode* right = NULL;
};
void InsertBST(TreeNode* &root, TreeNode*& s,int &count)
{
count++;
if (root == NULL) root = s;
else
{
if (s->data.id < root->data.id) InsertBST(root->left, s,count);
else InsertBST(root->right, s,count);
}
}
TreeNode* SearchBST(TreeNode* &root, string i)
{
if (root == NULL) return NULL;
else if (root->data.id == i) return root;
else if (i < root->data.id) return SearchBST(root->left, i);
else return SearchBST(root->right, i);
}
void EventManagement::Find1()
{
int count = 0;
string i;
cout << "请输入队伍编号:" << endl;
cin >> i;
cout << "----------------------------------------------------" << endl;
TreeNode* root = NULL;
for (list<team>::iterator it =++teams.begin(); it != teams.end(); ++it)
{
TreeNode* s = new TreeNode;
s->data = *it;
s->left = NULL;
s->right = NULL;
InsertBST(root, s, count);
}
TreeNode* m;
m = SearchBST(root, i);
if (m == NULL) cout << "查找失败!" << endl;
else
{
cout << "参赛队编号:" << m->data.id << '\t' << "参赛作品名称:" << m->data.entry << '\t' << "参赛学校:" << m->data.school << '\t' << "赛事类别:" << m->data.category << '\t' << "参赛者:" << m->data.participant << '\t' << "指导老师:" << m->data.teacher << endl;
cout << "平均查找长度ASL=count/(teams.size()-1)=" <<count<<"/"<< teams.size() - 1 <<"=" << (double)count / (teams.size() - 1) << endl;//之所以减一,是因为第一个存储的是数据项
}
system("pause");
system("cls");
}
首先,我定义了一个链表,将学校名称符合的队伍,通过遍历进行存储,然后根据编号排序。但因为是自定义类型,所以如果只调用sort()函数,程序无法知道应该如何排序。所以需要重新写排序规则。
//按参赛学校查询参赛团队
bool MyCompare(team t1, team t2)
{
return t1.id < t2.id;
}
void EventManagement::Find2()
{
string i;
cout << "请输入学校名称:" << endl;
cin >> i;
cout << "----------------------------------------------------" << endl;
list<team> t;
for (list<team>::iterator it = ++teams.begin(); it != teams.end(); ++it)
{
if (it->school == i)
t.push_back(*it);
}
if (t.empty()) cout << "未查找到该学校" << endl;
else
{
t.sort(MyCompare);
for (list<team>::iterator it = t.begin(); it != t.end(); ++it)
{
team x = *it;
cout << "参赛队编号:" << x.id << '\t' << "参赛作品名称:" << x.entry << '\t' << "参赛学校:" << x.school << '\t' << "赛事类别:" << x.category << '\t' << "参赛者:" << x.participant << '\t' << "指导老师:" << x.teacher << endl;
}
}
system("pause");
system("cls");
}
因为提供文件中的决赛室队伍,基本是按照参赛类别排好序的,所以我先将它们按照比赛类别进行排序,然后将它们平分到各个队列中。
//因为代码过多,所以我只列举决赛室1的叫号代码。
cout << "决赛室1的比赛顺序:" << endl;
while (count < t.size()/9&&it!=t.end())
{
cout << "参赛队编号:" << it->id << '\t' << "参赛作品名称:" << it->entry << '\t' << "参赛学校:" << it->school << '\t' << "赛事类别:" << it->category << '\t' << "参赛者:" << it->participant << '\t' << "指导老师:" << it->teacher << endl;
q1.push(*it);
count++;
it++;
}
while (!q1.empty()|| !q2.empty() || !q3.empty() || !q4.empty() || !q5.empty() || !q6.empty() || !q7.empty() || !q8.empty() || !q9.empty())
{
this_thread::sleep_for(chrono::milliseconds(500));
if (!q1.empty())
{
cout << "决赛室1:" << endl;
cout << "请下列队伍入场:" << q1.front().id << endl;
q1.pop();
if (!q1.empty()) cout << "请下列队伍准备:" << q1.front().id << endl;
}
//.........
}
对于建筑物信息查询算法,需要一个字符串类型的vector s 和另一个字符串类型的vector s1,分别存储了校园内各个建筑物的名称和简要信息。在主函数中,通过展示一个菜单,让用户输入所需查询建筑物的编号,然后根据这个编号去显示该建筑物的详细信息。
最短路径问题,需要设置地图的带权邻接矩阵为a[][],即如果从源点u到顶点i有边,就令a[u][i]=<u,i>的权值,否则a[u][i]=∞;采用一维数组dist[i]来记录从源点到i顶点的最短路径长度:采用一维数组path[i]来记录最短路径上i顶点的前驱。此外,还要定义数组flag[],记录节点是否被访问过。
(1)初始化:S={v},dist[v]=0,dist[j]=a[i][j];
(2)选择vk,使得dist[k]=min{dist[j]|vj∈V-S};
(3)S=SU{vk}
(4)对每个u∈V-S,u≠k,,若dist[k]+a[k][u]<dist[u],则修改dist[u]为dist[u]=dist[k]+a[k][u]
否则,不修改dist[u];
(5)重复(2)(3)(4),直到S=V。
//导游系统
#define Maxsize 11
#define Infinity 99999
void Dijksstra(int a[][Maxsize], int v0,int vg, int dist[], int path[])
{
int i, k,min, flag[Maxsize];
for (i = 0; i < Maxsize; i++)
{
dist[i] = a[v0][i];
if (dist[i] != Infinity) path[i] = v0;
else path[i] = -1;
}
path[v0] = -1;
flag[v0] = 1;
dist[v0] = 0;
int num = 1;
while (num < Maxsize)
{
k = 0;
min = Infinity;
for (i = 0; i < Maxsize; i++)
if((dist[i]<min)&&(flag[i]!=1))
{
min = dist[i];
k = i;
}
flag[k] = 1;
for(i=0;i<Maxsize;i++)
if (dist[k] + a[k][i] < dist[i])
{
dist[i] = dist[k] + a[k][i];
path[i] = k;
}
num++;
}
}
void shortest()
{
vector<string> s = { "49栋学生宿舍","西苑食堂","文体中心A","文体中心B","南门","文理大楼","计算机学院楼","笃学楼","东苑食堂","图书馆","北门" };
int v0, vg;
cout << "1.49栋学生宿舍\n2.西苑食堂\n3.文体中心A\n4.文体中心B\n5.南门\n6.文理大楼\n7.计算机学院楼\n8.笃学楼\n9.东苑食堂\n10.图书馆\n11.北门\n" ;
cout << "请输入出发地序号:" << endl;
cin >> v0;
cout << "请输入目的地序号:" << endl;
cin >> vg;
int a[Maxsize][Maxsize] ;
for (int i = 0; i < Maxsize; i++)
{
for (int j = 0; j < Maxsize; j++)
a[i][j] = Infinity;
}
a[0][1] = a[1][0] = 100;
a[0][2] = a[2][0] = 400;
a[1][3] = a[3][1] = 300;
a[1][5] = a[5][1] = 300;
a[2][3] = a[3][2] = 50;
a[2][4] = a[4][2] = 200;
a[3][7] = a[7][3] = 200;
a[4][7] = a[7][4] = 300;
a[5][7] = a[7][5] = 200;
a[7][8] = a[8][7] = 100;
a[7][9] = a[9][7] = 150;
a[8][9] = a[9][8] = 100;
a[5][6] = a[6][5] = 100;
a[6][10] = a[10][6] = 200;
a[9][10] = a[10][9] = 400;
int dist[Maxsize];
int path[Maxsize];
Dijksstra(a, v0 - 1, vg - 1, dist, path);
stack<int> st;
int x = vg-1;
st.push(x);
while (path[x] != -1)
{
x = path[x];
st.push(x);
}
cout << "最短路径为:";
while (!st.empty())
{
if (st.size() == 1) cout << st.top()+1 <<"." << s.at(st.top()) << endl;
else cout << st.top()+1 <<"." << s.at(st.top()) << "->";
st.pop();
}
cout << "最短路径值为:" << dist[vg-1] << endl;
system("pause");
system("cls");
}
void Check()
{
vector<string> s = { "49栋学生宿舍","西苑食堂","文体中心A","文体中心B","南门","文理大楼","计算机学院楼","笃学楼","东苑食堂","图书馆","北门" };
vector<string> s1 = {"分为A,B,C,D四栋","共三层,二楼食物比较丰富,三楼提供面食","文化艺术中心,有心理咨询室","体育馆,健身房","南侧大门","最高建筑物,有各类实验室","有计算机类实验室","日常学习教室","提供各类食物","提供各类书籍","北侧大门"};
while (1)
{
cout << "1.49栋学生宿舍\n2.西苑食堂\n3.文体中心A\n4.文体中心B\n5.南门\n6.文理大楼\n7.计算机学院楼\n8.笃学楼\n9.东苑食堂\n10.图书馆\n11.北门\n12.退出\n";
int x;
cout << "请输入要查询的建筑物编号:";
cin >> x;
cout << "----------------------------------------------------" << endl;
switch (x)
{
case 1:cout << s[x - 1] << ":" << s1[x - 1] << endl;
break;
case 2:cout << s[x - 1] << ":" << s1[x - 1] << endl;
break;
case 3:cout << s[x - 1] << ":" << s1[x - 1] << endl;
break;
case 4:cout << s[x - 1] << ":" << s1[x - 1] << endl;
break;
case 5:cout << s[x - 1] << ":" << s1[x - 1] << endl;
break;
case 6:cout << s[x - 1] << ":" << s1[x - 1] << endl;
break;
case 7:cout << s[x - 1] << ":" << s1[x - 1] << endl;
break;
case 8:cout << s[x - 1] << ":" << s1[x - 1] << endl;
break;
case 9:cout << s[x - 1] << ":" << s1[x - 1] << endl;
break;
case 10:cout << s[x - 1] << ":" << s1[x - 1] << endl;
break;
case 11:cout << s[x - 1] << ":" << s1[x - 1] << endl;
break;
case 12:return;
default:cout << "输入数据有误" << endl;
}
system("pause");
system("cls");
}
}
void EventManagement::Guid()
{
int x;
while (1)
{
system("cls");
cout << "----------------------------------------------------" << endl;
cout << "1.建筑物相关信息查询\n2.问路查询\n3.退出\n请输入选择的序号:";
cin >> x;
switch (x)
{
case 1:system("cls"); Check();
break;
case 2:system("cls"); shortest();
break;
case 3:system("cls"); return;
default:cout << "输入数据有误" << endl;
break;
}
}
}
void EventManagement::Show1()
{
for (list<team>::iterator it = ++teams.begin(); it != teams.end(); it++)
cout << "参赛队编号:" << it->id << "参赛作品名称:" << it->entry << "参赛学校:" << it->school << "赛事类别:" << it->category << "参赛者:" << it->participant << "指导老师:" << it->teacher << endl;
system("pause");
system("cls");
}