一、问题定义
中国大学生计算机设计大赛信息管理系统
二、问题分析
1.参赛队伍信息管理
使用链表存储队伍信息,每个节点存储一个队伍的基本信息,包括参赛队编号、参赛作品名称、参赛学校、赛事类别、参赛者、指导老师等。可以使用增删改操作来管理队伍信息。
对于题设要求“管理各参赛队的基本信息,对参赛队伍的信息进行增删改操作”,我们可以用队列或者链表来储存队伍信息,再进行增删改操作。但是基于对链表的增删改操作更容易实现,我们选择用链表来储存队伍信息。
2.基于二叉排序树的查找
使用二叉排序树来实现查找,按照参赛队编号进行排序。从team.txt文件中读取队伍信息并插入到二叉排序树中,根据提示输入参赛队编号,查找到对应的队伍后输出基本信息,并计算平均查找长度ASL。
3.参赛学校或赛事类别查询
通过链表中存储的队伍信息,按照参赛学校或赛事类别进行排序,并使用选择排序、插入排序、希尔排序、归并排序、堆排序等排序算法进行实现。根据提示输入参赛学校或赛事类别,查找到对应的队伍后输出基本信息,按照赛事类别有序输出。
在此场景中,可以选择使用稳定的归并排序来进行排序,归并排序具有以下特点:
Ⅰ. 时间复杂度较低:归并排序的时间复杂度为O(n log n),与其他高效的排序算法相比,例如快速排序和堆排序,归并排序的性能相对均衡,同时在worst-case的情况下,它始终具有最佳性能。
Ⅱ.归并排序是稳定排序:在排序中,稳定排序的意思是具有相同键值的元素按照它们在原始数据中的顺序出现在排序结果中。这对于参赛学校查询参赛团队的场景非常重要,在赛事类别有序输出的前提下,按照学校排序也需要保证稳定。
Ⅲ. 归并排序不需要额外空间:归并排序通常需要额外的空间来存储数组(或链表)的两个子部分,但它相对于其他需要额外空间的算法而言,比如归并排序的额外空间的大小不会超过原始数据大小的常数倍,且不大于新元素的空间需求。此外,归并排序对于额外空间的需求可以通过不断地合并子列表来降低,这也有助于减少额外空间的使用。
4.决赛叫号系统
使用队列来实现决赛叫号系统。根据赛事类别将所有参赛队分成9组,在每个决赛室中使用队列进行叫号,按照顺序叫号,叫号后队伍进场比赛。演示叫号过程时,要保证叫号顺序与队伍进场顺序一致。
在这个场景中,最好使用队列来实现决赛叫号系统,因为队列是先进先出的数据结构,可以很好地模拟参赛队按照顺序进场的过程。当所有参赛队伍分组完成后,每个决赛室通过一个队列来进行叫号。当一个参赛队伍被叫号后,就会进入队列的末尾,等待上一支参赛队伍比赛结束后,依次进入比赛场地。使用栈来实现这个场景是不太可行的。虽然栈也是一种可以模拟先进后出的数据结构,但是栈不满足“先进先出”的要求,无法完美地模拟参赛队按照队列进场的过程。
5.校园导游程序
使用Dijkstra算法来实现最短路径的查询服务。首先将校园地图中所有建筑物及其之间的距离存储在图中,然后根据提示输入任意两个建筑物的名称,查询它们之间的最短路径。同时可以提供建筑物相关信息的查询服务,根据输入的建筑物名称输出相应的信息。
求最短路径的算法有多种:Dijkstra算法、Floyd-Warshall算法、Bellman-Ford算法、A*算法等,在此场景中,选择采用Dijkstra算法的原因如下:
Ⅰ.Dijkstra算法适用于求解正权有向图的单源最短路径,而在该场景中,我们需要求解的是校园中任意两个建筑物之间的最短路径,因此Dijkstra算法适用。
Ⅱ.Dijkstra算法的时间复杂度是O(n^2),n为图中节点数。对于校园地图这样的小规模图,Dijkstra算法的运算速度可以满足需求。
Ⅲ. Dijkstra算法可以处理带有负权边的图,而在实际情况中,可能存在建筑物之间有非常短的折返路程,形成负权边,使用Dijkstra算法可以处理这种情况。
Ⅳ.Dijkstra算法保证对于非负边权的图,求解结果一定是正确的。在实际场景中,校园建筑物之间的距离或时间都是正数,因此这个保证在这个场景中是成立的。
三、概要设计
这是一个比较复杂的系统,需要使用多种数据结构和算法来实现。下面是一个大致的设计思路,供参考:
1.参赛队伍信息管理
管理各参赛队的基本信息可以使用链表数据结构实现,每个结点存储一个参赛队的基本信息,包括参赛队编号、参赛作品名称、参赛学校、赛事类别、参赛者和指导老师等信息。同时,也可以采用队列数据结构,将参赛队按照加入的顺序依次排列。
具体实现参考伪代码:
// 参赛队伍信息的结构体定义
struct team {
int team_id; // 参赛队编号
string team_name; // 参赛作品名称
string school_name; // 参赛学校
int competition_type; // 赛事类别代码
string participants; // 参赛者
string instructor; // 指导老师
struct team* next; // 指向下一个结点的指针
};
// 创建一个新结点并初始化它
struct team* create_new_node(int team_id, string team_name, string school_name, int competition_type, string participants, string instructor) {
struct team* new_node = new team;
new_node->team_id = team_id;
new_node->team_name = team_name;
new_node->school_name = school_name;
new_node->competition_type = competition_type;
new_node->participants = participants;
new_node->instructor = instructor;
new_node->next = NULL;
return new_node;
}
// 添加一个新结点到链表中
void add_team(struct team** head, int team_id, string team_name, string school_name, int competition_type, string participants, string instructor) {
struct team* new_node = create_new_node(team_id, team_name, school_name, competition_type, participants, instructor);
// 如果链表还没创建,直接把新结点设为头结点
if ((*head) == NULL) {
(*head) = new_node;
return;
}
// 找到链表尾部, 插入新的结点
struct team* temp = (*head);
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = new_node;
}
// 根据参赛队编号删除参赛队伍信息
void delete_team(struct team** head, int team_id) {
struct team* temp = (*head);
struct team* prev = NULL;
// 找到要删除的结点,如果找到,把 prev 指针指向 prev->next,跳过该结点
while (temp != NULL && temp->team_id != team_id) {
prev = temp;
temp = temp->next;
}
// 如果没找到要删除的结点,则直接返回
if (temp == NULL) {
return;
}
// 删除头结点
if (prev == NULL) {
(*head) = temp->next;
free(temp);
return;
}
// 删除非头结点
prev->next = temp->next;
free(temp);
}
// 根据参赛队编号修改参赛队伍信息
void update_team(struct team** head, int team_id, string team_name, string school_name, int competition_type, string participants, string instructor) {
struct team* temp = (*head);
// 找到要修改的结点,并修改它的信息
while (temp != NULL && temp->team_id != team_id) {
temp = temp->next;
}
if (temp != NULL) {
temp->team_name = team_name;
temp->school_name = school_name;
temp->competition_type = competition_type;
temp->participants = participants;
temp->instructor = instructor;
}
}
此外,可以通过读写文件,将参赛队伍信息永久存储在本地磁盘中,提高系统可靠性和数据持久性。
2.基于二叉排序树的查找
具体的代码实现:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
// 定义二叉排序树节点类
class BSTNode {
public:
int team_no; // 参赛队编号
string team_name; // 参赛作品名称
string school; // 参赛学校
string race; // 赛事类别
string members; // 参赛者
string coach; // 指导老师
BSTNode* left; // 左子节点
BSTNode* right; // 右子节点
// 构造函数
BSTNode(int no, string name, string s, string r, string m, string c) : team_no(no), team_name(name), school(s), race(r), members(m), coach(c), left(NULL), right(NULL) {}
};
// 定义二叉排序树类
class BST {
public:
BSTNode* root; // 根节点
// 构造函数
BST() : root(NULL) {}
// 插入节点
void insert(int team_no, string team_name, string school, string race, string members, string coach) {
BSTNode* node = new BSTNode(team_no, team_name, school, race, members, coach);
if (root == NULL) {
root = node;
} else {
BSTNode* cur = root;
while (true) {
if (team_no < cur->team_no) {
if (cur->left == NULL) {
cur->left = node;
break;
} else {
cur = cur->left;
}
} else {
if (cur->right == NULL) {
cur->right = node;
break;
} else {
cur = cur->right;
}
}
}
}
}
// 查找节点并计算ASL
bool search(int team_no, BSTNode*& node, int& depth, int& count) {
depth = 0;
count = 0;
node = root;
while (node != NULL) {
depth++;
count += depth;
if (team_no == node->team_no) {
return true;
} else if (team_no < node->team_no) {
node = node->left;
} else {
node = node->right;
}
}
return false;
}
};
int main() {
// 读取team.txt中的数据并构建二叉排序树
BST bst;
ifstream in("team.txt");
if (in.is_open()) {
int team_no;
string team_name, school, race, members, coach;
while (in >> team_no >> team_name >> school >> race >> members >> coach) {
bst.insert(team_no, team_name, school, race, members, coach);
}
in.close();
} else {
cout << "无法打开文件!" << endl;
return 0;
}
// 输入参赛队编号进行查找
int no;
cout << "输入参赛队编号:";
cin >> no;
// 查找节点并计算ASL
BSTNode* node;
int depth, count;
bool found = bst.search(no, node, depth, count);
// 输出查找结果
if (found) {
cout << "查找成功:" << endl;
cout << "参赛作品名称:" << node->team_name << endl;
cout << "参赛学校:" << node->school << endl;
cout << "赛事类别:" << node->race << endl;
cout << "参赛者:" << node->members << endl;
cout << "指导老师:" << node->coach << endl;
cout << "平均查找长度ASL:" << count / (double)depth << endl;
} else {
cout << "查找失败!" << endl;
}
return 0;
}
以上代码中通过文件流读取team.txt中的数据,并通过BST类的insert方法构建二叉排序树。接着,通过BST类中的search方法实现从根节点开始查找并计算ASL。最后根据查找结果输出相应的信息。
3.参赛学校或赛事类别查询
算法设计:
Ⅰ.从team.txt中读取参赛团队信息,构建二叉排序树。
Ⅱ.基于二叉排序树,实现根据参赛学校名称或赛事类别查询参赛团队,并将查询结果存储到一个vector中。(同2操作)
Ⅲ.对查询结果按赛事类别进行归并排序并输出。
实现步骤:
Ⅰ.定义二叉排序树节点类BSTNode,包含参赛团队的各项基本信息,以及左右子树指针。
Ⅱ.定义二叉排序树类BST,包含根节点指针(BSTNode* root),以及insert方法用于在树中插入节点。
Ⅲ.从team.txt中读取参赛团队信息,构建BST。
Ⅳ.实现函数searchBySchool和searchByRace,分别用于根据参赛学校和赛事类别查询参赛团队,并将查询结果存储到一个vector中。
Ⅴ.实现函数mergeSortByRace,用于按照赛事类别对查询结果进行归并排序并输出。
以下是伪代码实现:
//定义二叉排序树节点类
class BSTNode {
public:
string teamName;
string schoolName;
string race;
//其他基本信息
BSTNode* left;
BSTNode* right;
BSTNode(string teamName, string schoolName, string race){
this->teamName = teamName;
this->schoolName = schoolName;
this->race = race;
//初始化其他信息
left = nullptr;
right = nullptr;
}
};
//定义二叉排序树类
class BST {
public:
BSTNode* root;
BST(){
root = nullptr;
}
void insert(BSTNode* node){
if(root == nullptr){
root = node;
return;
}
BSTNode* curr = root;
while(true){
if(node->teamName < curr->teamName){
if(curr->left == nullptr){
curr->left = node;
break;
}
else
curr = curr->left;
}
else{
if(curr->right == nullptr){
curr->right = node;
break;
}
else
curr = curr->right;
}
}
}
};
//读取team.txt并构建二叉排序树
BST readTeamsFromFile(string fileName){
BST bst;
ifstream fin(fileName);
string line;
while(getline(fin, line)){
//解析line并创建节点
BSTNode* node = new BSTNode(teamName, schoolName, race);
bst.insert(node);
}
fin.close();
return bst;
}
//根据学校名称查询团队
vector<BSTNode*> searchBySchool(BSTNode* node, string schoolName){
vector<BSTNode*> result;
if(node == nullptr)
return result;
if(node->schoolName == schoolName){
result.push_back(node);
}
vector<BSTNode*> subResultLeft = searchBySchool(node->left, schoolName);
vector<BSTNode*> subResultRight = searchBySchool(node->right, schoolName);
result.insert(result.end(), subResultLeft.begin(), subResultLeft.end());
result.insert(result.end(), subResultRight.begin(), subResultRight.end());
return result;
}
//根据赛事类别查询团队
vector<BSTNode*> searchByRace(BSTNode* node, string race){
vector<BSTNode*> result;
if(node == nullptr)
return result;
if(node->race == race){
result.push_back(node);
}
vector<BSTNode*> subResultLeft = searchByRace(node->left, race);
vector<BSTNode*> subResultRight = searchByRace(node->right, race);
result.insert(result.end(), subResultLeft.begin(), subResultLeft.end());
result.insert(result.end(), subResultRight.begin(), subResultRight.end());
return result;
}
//按赛事类别对查询结果进行归并排序并输出
void mergeSortByRace(vector<BSTNode*>& teams){
//实现归并排序并输出结果
}
//调用函数查询并输出结果
int main(){
BST bst = readTeamsFromFile("team.txt");
//根据学校名称查询并输出
vector<BSTNode*> result1 = searchBySchool(bst.root, "清华大学");
mergeSortByRace(result1);
//根据赛事类别查询并输出
vector<BSTNode*> result2 = searchByRace(bst.root, "篮球");
mergeSortByRace(result2);
return 0;
}
4.决赛叫号系统
使用队列来实现决赛叫号系统。根据赛事类别将所有参赛队分成9组,在每个决赛室中使用队列进行叫号,按照顺序叫号,叫号后队伍进场比赛。演示叫号过程时,要保证叫号顺序与队伍进场顺序一致。
根据赛事组织文件中的赛事类别分组,将所有参赛队分成9个组。假设每个组有20支参赛队,编号为1~20。为了模拟决赛叫号系统,我们可以采用队列来存储每个决赛室的队伍。对每一轮比赛,我们首先按顺序叫号进场,然后等待当前比赛结束后再进行下一轮。
具体实现步骤如下:
1. 首先将所有参赛队编号放入到对应组的队列中。
2. 轮流选取每个决赛室的队列,按照固定的顺序叫号进场,同时输出显示相应的参赛队编号。
3. 等待当前比赛结束后才能进行下一轮叫号进场。
以下是代码示例:
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int N_GROUP = 9; // 参赛队伍分组数
const int N_PER_GROUP = 20; // 每个组参赛队伍数量
const int N_ROOM = 5; // 决赛室数量
int main()
{
// 定义队列,存储各个决赛室的参赛队编号
queue<int> rooms[N_ROOM];
for (int i = 0; i < N_GROUP; i++) {
// 将每个组的参赛队编号平均放入到对应的决赛室队列中
for (int j = 0; j < N_PER_GROUP / N_ROOM; j++) {
for (int k = 0; k < N_ROOM; k++) {
rooms[k].push(i * N_PER_GROUP / N_ROOM + j + 1);
}
}
}
// 循环叫号进场
for (int i = 0; i < N_PER_GROUP / N_ROOM; i++) {
for (int j = 0; j < N_ROOM; j++) {
// 取出当前决赛室队列队首的参赛队编号,进场
int teamNo = rooms[j].front();
cout << "第" << teamNo << "支参赛队伍进入第" << (j + 1) << "个决赛室" << endl;
// 将该参赛队伍放到队列尾,等待下一次轮流进场
rooms[j].pop();
rooms[j].push(teamNo);
}
// 等待当前比赛结束
cout << "等待当前比赛结束..." << endl;
// 此处可以模拟比赛耗时
}
return 0;
}
5.校园导游程序
这个问题需要使用图论算法和数据结构来实现,以下是一种常见的解决方案:
1. 创建一个图对象表示校园地图,其中节点表示建筑物,边表示建筑物之间的通行道路,边权表示两个建筑物之间的距离。
2. 使用数据结构(例如数组或哈希表)来存储节点名字和其在图中对应的节点编号,这样可以快捷地进行节点的查找。
3. 实现图的构建:根据校园地图上的建筑物位置和通行道路信息等数据,通过遍历把建筑物按位置映射为节点,并把通行道路按照一定的规则加入图中,将通行道路长度作为边权。
4. 为处理最短路径问题,使用迪杰斯特拉算法(Dijkstra Algorithm)或者A*搜索算法(A*search Algorithm)解决,可以求出两个建筑物之间的最短路径。
5. 为了查询任意两个节点之间的路径,可以使用上述算法计算两节点之间的最短路径,并记录路径上的所有节点,再根据节点找到相应的建筑物名称,最终把路径上的建筑物串成一条导航路线返回给用户。
下面是使用邻接矩阵表示法构建图对象,同时使用Dijkstra算法求最短路径的C++代码实现:
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;
int get_index(vector<string>& nodes, string target) {
for (int i = 0; i < nodes.size(); i++) {
if (nodes[i] == target) {
return i;
}
}
return -1;
}
void build_map(vector<string>& nodes, vector<vector<int>>& map) {
// 建立地点名称和编号之间的映射关系
int node_count = nodes.size();
vector<vector<int>> new_map(node_count, vector<int>(node_count, INT_MAX));
for (int i = 0; i < node_count; i++) {
new_map[i][i] = 0;
}
nodes.push_back("出口"); // 添加一个出口节点
new_map.push_back(vector<int>(node_count+1, INT_MAX));
for (int i = 0; i < node_count+1; i++) {
new_map[i][node_count] = new_map[node_count][i] = INT_MAX;
}
// 添加建筑物和路径数据,建立地图
new_map[0][1] = new_map[1][0] = 300; // 添加1号楼到2号楼的路径
new_map[0][3] = new_map[3][0] = 500; // 添加1号楼到3号楼的路径
new_map[1][4] = new_map[4][1] = 150; // 添加2号楼到4号楼的路径
new_map[2][6] = new_map[6][2] = 200; // 添加教学楼到6号楼的路径
new_map[3][4] = new_map[4][3] = 400; // 添加3号楼到4号楼的路径
new_map[4][6] = new_map[6][4] = 350; // 添加4号楼到6号楼的路径
new_map[4][7] = new_map[7][4] = 450; // 添加4号楼到7号楼的路径
new_map[5][6] = new_map[6][5] = 150; // 添加图书馆到6号楼的路径
new_map[5][7] = new_map[7][5] = 300; // 添加图书馆到7号楼的路径
// 更新原图
nodes = nodes; // 后续还需要添加其他建筑物
map = new_map;
}
void dijkstra(vector<string>& nodes, vector<vector<int>>& map, int start, int end) {
int node_count = nodes.size();
// 初始化距离数组
vector<int> dist(node_count, INT_MAX);
dist[start] = 0;
// 初始化访问数组
vector<bool> visited(node_count, false);
// 初始化路径数组
vector<int> path(node_count, -1);
// 依次访问每个节点
for (int i = 0; i < node_count; i++) {
// 从未访问节点中选取距离起点最近的节点
int min_dist = INT_MAX;
int current = -1;
for (int j = 0; j < node_count; j++) {
if (!visited[j] && dist[j] < min_dist) {
min_dist = dist[j];
current = j;
}
}
if (current == -1) {
break; // 所有节点均已访问过,退出循环
}
visited[current] = true; // 标记当前节点已访问
// 以当前节点为中心,更新所有邻接节点的距离
for (int k = 0; k < node_count; k++) {
if (map[current][k] != INT_MAX) {
int new_dist = dist[current] + map[current][k];
if (new_dist < dist[k]) {
dist[k] = new_dist; // 更新到起点的最短距离
path[k] = current; // 通过哪个节点到达当前节点
}
}
}
}
// 根据path数组回溯路径
if (path[end] != -1) {
cout << nodes[end] << "<-";
int p = path[end];
while (p != start) { // 注意判断循环结束条件
cout << nodes[p] << "<-";
p = path[p];
}
// 输出起点
cout << nodes[start] << endl;
}
// 继续输出路径上的节点
if (p == start) {
cout << nodes[start] << endl;
break;
}
cout << nodes[p] << "<-";
p = path[p];
}
// 输出最短距离
cout << "最短距离为:" << dist[end] << endl;
}
int main() {
vector<string> nodes = {"1号楼", "2号楼", "3号楼", "4号楼", "5号楼", "图书馆", "教学楼"};
vector<vector<int>> map;
build_map(nodes, map);
// 查询任意两个建筑物之间的最短路径
int start = get_index(nodes, "1号楼");
int end = get_index(nodes, "教学楼");
cout << "1号楼到教学楼的最短路径为:";
dijkstra(nodes, map, start, end);
start = get_index(nodes, "4号楼");
end = get_index(nodes, "5号楼");
cout << "4号楼到5号楼的最短路径为:";
dijkstra(nodes, map, start, end);
start = get_index(nodes, "图书馆");
end = get_index(nodes, "出口");
cout << "图书馆到出口的最短路径为:";
dijkstra(nodes, map, start, end);
return 0;
}
如果导航服务要求更加精确,可以考虑采用高精度地图和GPS技术,结合路线规划算法实现更加真实和准确的路径导航服务。