文章目录
项目一:计算机设计大赛赛事统计
问题分析及任务定义
【问题描述】
参加计算机设计大赛的n个学校编号为1-n,赛事分成m个项目,项目的编号为1-m.比赛获奖按照得分降序,取前三名,写一个统计程序产生各种成绩单和得分报表。
【基本要求】
- 每个比赛项目至少有10支参赛队;每个学校最多有6支队伍参赛; 能统计各学校的总分;
- 可以按照学校编号或名称查询,学校的总分、各项目的总分排序输出;
- 可以按学校编号查询学校某个项目的获奖情况;
- 可以按项目编号查询取得前三名的学校;
- 数据存入文件并能随时查询
【设计要求】
- 输入数据形式和范围:可以输入学校的名称,赛事项目的名称。
- 输出形式:有中文提示,各学校分数为整数
- 界面要求:交互设计要合理,每个功能可以设菜单,根据提示,可以完成相关功能的要求。
- 存储结构:学生自己根据系统功能要求自己设计,但是赛事相关数据要存储在文件中。
数据结构的选择和概要设计
【抽象数据类型(ADT)】
ADT team
Data:
非负整数表示学校编号;
字符串表示学校名称;
非负整数表示项目编号;
字符串表示项目名称;
非负整数表示项目得分;
非负整数表示队伍编号;
指向链表下一个结点的指针 ;
endADT
ADT project
Data:
指向team结点的指针;
非负整数表示项目编号;
Primary Operation:
load
前提条件:无
输入:读取文件
功能:构建项目链表
输出:无
后置条件:无
Setproject_id
前提条件:无
输入:无
功能:设置项目编号
输出:无
后置条件:无
count_id
前提条件:无
输入:学校编号
功能:根据学校编号统计该项目中某学校总分
输出:该项目某学校总分
后置条件:无
count_name
前提条件:无
输入:学校名称
功能:根据学校名称统计该项目中某学校总分
输出:该项目某学校总分
后置条件:无
sort_score
前提条件:无
输入:无
功能:将除头结点外的链表结点根据项目得分降序排序
输出:无
后置条件:无
endADT
ADT System
Data
所有项目创建链表的头结点构成的数组
非负整数表示项目数量
非负整数表示学校数量
关联容器1,键为非负整数学校编号,值为学校名称字符串
关联容器2,键为非负整数项目编号,值为项目名称字符串
Primary Operation:
initialize_map1
前提条件:无
输入:读取文件
功能:将文件中学校编号与学校名称对应输入关联容器1
输出:无
后置条件:无
void initialize_map2
前提条件:无
输入:读取文件
功能:将文件中学校编号与学校名称对应输入关联容器1
输出:无
后置条件:无
Count_re
前提条件:无
输入:无
功能:统计每一所学校所有项目总分
输出:指向每一所学校所有项目总分构成整数数组的指针
后置条件:无
void Count_vo
前提条件:无
输入:无
功能:统计每一所学校所有项目总分
输出:输出每一所学校所有项目总分
后置条件:无
lookup_school
前提条件:无
输入:控制台输入学校编号,项目编号
功能:查询某一学校某一项目获奖情况
输出:输出某一学校某一项目的获奖情况
后置条件:无
void lookup_project
前提条件:无
输入:控制台输入
功能:查询某一项目的获奖者
输出:输出某一项目的获奖者
后置条件:无
lookup_school_id
前提条件:无
输入:控制台输入
功能:根据学校编号统计某一所学校各项目的总分
输出:输出某一所学校各项目的总分
后置条件:无
void lookup_school_name
前提条件:无
输入:控制台输入
功能:根据学校名称统计某一所学校各项目的总分
输出:输出某一所学校各项目的总分
后置条件:无
storage
前提条件:无
输入:无
功能:将结果输入到txt文件中
输出:文件输出
后置条件:无
add
前提条件:无
输入:控制台输入和文件输入
功能:根据控制台输入项目名称和文件输入数据增加新项目参赛信息
输出:无
后置条件:无
endADT
【主要模块算法】
sort_score()
功能:使用归并算法进行链表排序
算法详解:
- 先用数组举例讲解归并排序,要将一个数组排序,可以先(递归的)将它分成两半分别排序,然后将结果归并起来。
- 归并排序最吸引人的地方性质是它能够保证将任意长度为N的数组排序所需时间和NlogN成正比。
- 接下里阐述链表与数组使用归并算法的不同之处。
- 首先是分割链表时候,我采用的是快慢指针法,慢指针一次走一步,快指针一次走两步,当快指针达到末尾的时候,慢指针正好会指向中间节点,只用一次遍历。
node* slow = head;
node* fast = head->next;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
node* r_head = slow->next;
slow->next = nullptr;
- 接下来是处理合并,我们只需要调整链表结点间的指向关系就能完成
node* merge(node* left, node* right) {
node* head = new node;
node* t = head;
while (left && right) {
if (left->score > right->score) {
t->next = left;
left = left->next;
}
else {
t->next = right;
right = right->next;
}
t = t->next;
}
t->next = left == nullptr ? right : left;
return head->next;
}
- 最后是递归实现链表归并算法的全部代码
node* merge(node* left, node* right) {
node* head = new node;
node* t = head;
while (left && right) {
if (left->score > right->score) {
t->next = left;
left = left->next;
}
else {
t->next = right;
right = right->next;
}
t = t->next;
}
t->next = left == nullptr ? right : left;
return head->next;
}
node* merge_sort(node* head) {
if (!head->next)
return head;
node* slow = head;
node* fast = head->next;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
node* r_head = slow->next;
slow->next = nullptr;
node* left = merge_sort(head);
node* right = merge_sort(r_head);
return merge(left, right);
}
【函数调用关系图】
3.详细设计和编码
【详细说明主要数据结构和基本操作】
【代码(C++)】
4.测试结果及其分析
【测试案例】
【测试结果】
5.用户使用说明
6.参考文献
7.附录
项目二:校园导游咨询
问题分析及任务定义
【问题描述】
设计一个校园导游程序,为来访的客人提供各种信息查询服务。
【基本要求】
- 设计你所在学校的校园平面图,所含景点不少于10个. 以图中顶点表示校内各景点,存放景点名称、代号、简介 等信息;以边表示路径,存放路径长度等相关信息。
- 为来访客人提供图中任意景点相关信息的查询。
- 为来访客人提供图中任意景点的问路查询,即查询任意两个景点之间的一条最短的简单路径。
数据结构的选择和概要设计
【抽象数据类型(ADT)】
ADT vertex
Data
字符表示景点代号
字符串表示景点名称
字符串表示景点的信息
endADT
ADT System
Data
非负整数表示图中景点数量
vertex数组存放所有景点信息
二维整数数组表示邻接矩阵
Primary Operation:
initialization_w
前提条件:无
输入:读取文件
功能:读取文件设置邻接矩阵的值并把非主对角线上为0处设置为最大值
输出:无
后置条件:无
initialization_v
前提条件:无
输入:读取文件
功能:读取文件初始化vertex数组元素
输出:无
后置条件:无
floyd
前提条件:无
输入:起止位置
功能:使用floyd算法求取两个景点间的最短路径
输出:无
后置条件:无
getpath
前提条件:无
输入:起止位置,路径矩阵,景点数量
功能:通过floyd算法得到的path矩阵通过递归求出路径
输出:最短路径
后置条件:无
find_info
前提条件:无
输入:控制台输入
功能:查询某一个景点的信息
输出:景点信息
后置条件:无
endADT
【主要模块算法】
floyd()
功能:求出无向图两点间最短路径
算法详解:
-
设A为存放图中所有顶点之间的最短路径长度的n阶方阵。
-
初始时,A等于图G的邻接矩阵,即A=w。取k=0,1,…,n-1,对A中的每个元素A[i][j]做如下计算:
A[i][j]=min{A[i][j],A[i][k]+A[k][j]}
-
它相当于随着k=0,1…,n-1,产生n个不同的矩阵,后一个矩阵中每一个元素都是由前一个矩阵按上述公式计算而得。而最后的结果矩阵中的元素A[i][j]保存的就是从i到j的最短路径长度。
-
另设置一个n×n二维数组path ,path[i][j]保存的是由顶点i到顶点j的最短路径中i的直接后继。
-
它的调整规律是:若将k作为中间点,路径i…k…j的长度比i…(k-1)…j的长度小时,即A[i][j]<A[i][k]+A[k][j]时,path[i][j]=path[i][k],表明从i到j的最短路径要加以调整时,从i到j的最短路径中间要经过k,从i到j的最短路径上i的直接后继就是从i到k的最短路径上i的直接后继。否则不做调整。
-
floyd算法的c++实现
int** dis = new int* [vertex_num];
int** path = new int* [vertex_num];
for (int i = 0; i < vertex_num; i++) {
dis[i] = new int[vertex_num];
}
for (int i = 0; i < vertex_num; i++) {
path[i] = new int[vertex_num];
}
for (int i = 0; i < vertex_num; i++) {
for (int j = 0; j < vertex_num; j++) {
dis[i][j] = weight[i][j];
path[i][j] = i;
}
}
int temp = 0;
for (int k = 0; k < vertex_num; k++) {
for (int i = 0; i < vertex_num; i++) {
for (int j = 0; j < vertex_num; j++) {
if (dis[i][k] == INT_MAX || dis[k][j] == INT_MAX) {
temp = INT_MAX;
}
else {
temp = dis[i][k] + dis[k][j];
}
if (temp < dis[i][j]) {
dis[i][j] = temp;
path[i][j] = path[k][j];
}
}
}
}
- 路径查找的c++具体实现
if (path[start][end] == start) {
cout << vexs[start].name << "->"<<vexs[end].name<<"->";
}
else {
getpath(start,path[start][end],path,vertex);
cout << vexs[end].name << "->";
}
- 查找最小路径的全部代码
void graph::getpath(int start, int end, int** path, int vertex)
{
if (path[start][end] == start) {
cout << vexs[start].name << "->"<<vexs[end].name<<"->";
}
else {
getpath(start,path[start][end],path,vertex);
cout << vexs[end].name << "->";
}
}
void graph::floyd(int start, int end)
{
int** dis = new int* [vertex_num];
int** path = new int* [vertex_num];
for (int i = 0; i < vertex_num; i++) {
dis[i] = new int[vertex_num];
}
for (int i = 0; i < vertex_num; i++) {
path[i] = new int[vertex_num];
}
for (int i = 0; i < vertex_num; i++) {
for (int j = 0; j < vertex_num; j++) {
dis[i][j] = weight[i][j];
path[i][j] = i;
}
}
int temp = 0;
for (int k = 0; k < vertex_num; k++) {
for (int i = 0; i < vertex_num; i++) {
for (int j = 0; j < vertex_num; j++) {
if (dis[i][k] == INT_MAX || dis[k][j] == INT_MAX) {
temp = INT_MAX;
}
else {
temp = dis[i][k] + dis[k][j];
}
if (temp < dis[i][j]) {
dis[i][j] = temp;
path[i][j] = path[k][j];
}
}
}
}
cout << "从" << vexs[start].name << "到" << vexs[end].name << "的最短路径路线依次如下:" << endl;
getpath(start, end, path, vertex_num);
}
void graph::find_min()
{
cout << "图中所有景点如下" << endl;
for (int i = 0; i < vertex_num; i++) {
cout << vexs[i].name << "(" << vexs[i].id << ")" <<" ";
}
char choice1;
cout << "请输入当前景点代号(代号见上):";
while (cin >> choice1) {
if (choice1 <= 64 + vertex_num && choice1 >= 65)
break;
cout << "输入异常,请重新输入。";
}
char choice2;
cout << "请输入目标景点代号(代号见上):";
while (cin >> choice2) {
if (choice2 <= 64 + vertex_num && choice2 >= 65)
break;
cout << "输入异常,请重新输入。";
}
floyd(choice1 - 65, choice2 - 65);
}
【函数调用关系图】
3.详细设计和编码
【详细说明主要数据结构和基本操作】
【代码(C++)】
4.测试结果及其分析
【测试案例】
【测试结果】
5.用户使用说明
6.参考文献
7.附录
项目三:算术表达式求解
问题分析及任务定义
【问题描述】
设计一个简单的算术表达式计算器。
【基本要求】
实现标准整数类型的四则运算表达式的求值(包含括号,可多层嵌入)
数据结构的选择和概要设计
【抽象数据类型(ADT)】
ADT Calculation
Data
字符串表示要求解的表达式
Primary Operation:
pretreat
前提条件:无
输入:表达式字符串
功能:处理表达式的格式(包括处理空格,处理括号不匹配,处理负数,处理未知符号等)
输出:修改后标准的表达式或者退出程序
后置条件:无
isNumber
前提条件:无
输入:字符
功能:判断输入字符是否为运算数
输出:是运算数为1,反之为0
后置条件:无
isOpt
前提条件:无
输入:字符
功能:判断输入字符是否为运算符
输出:是运算符为1,反之为0
后置条件:无
opt_prior
前提条件:无
输入:字符
功能:判断输入运算符的优先级
输出:返回输入运算符的优先数
后置条件:无
infix_to_posfix
前提条件:无
输入:处理过后的算术表达式
功能:将中缀表达式转为后缀表达式
输出:返回后缀表达式
后置条件:无
posfix_calculation
前提条件:无
输入:后缀表达式字符串
功能:计算后缀表达式
输出:返回运算结果
后置条件:无
endADT
【主要模块算法】
infix_to_posfix()
首先创建一个栈用于暂存运算符,创建一个字符串容器存放后缀表达式
中缀表达式转后缀表达式遵循以下几个规则遍历表达式字符串即可
- 遍历到的字符为运算符(+,-,*,/,^):
如果运算符栈为空或者栈顶为左括号,则直接把改运算符压入栈中;
否则将该运算符依次与栈中元素的优先级进行对比,如果栈顶元素较大则将栈顶元素抛出至后缀表达式尾端;
直至遇到比该元素优先级小的运算符,将该运算符压入栈中; - 遍历到的字符为左括号
将左括号压入栈中 - 遍历到字符为右括号
将栈中元素自顶向下依次取出放入后缀表达式容器尾端,直至遇到左括号;
抛出左括号 - 遍历到字符为数字
如果中缀表达式下一个字符为数字或者小数点,则合并两个字符,直至遇上下一个字符不为数字或者小数点;
将最后合并结果放入后缀或表达式容器尾端 - 遇上Π和e
将对应近似值放入容器尾端 - 遍历结束后,将栈中剩余字符自顶向下依次放入后缀表达式容器尾端
- 中缀表达式转后缀表达式的c++实现
//这里采用链表来存储后缀表达式
node* infix_to_posfix(string expression) {
node* head = new node;
stack<char> opt;
for (int i = 0; i < expression.length(); i++) {
string stemp = "";
if (expression[i] == '+' || expression[i] == '-'
|| expression[i] == '*' || expression[i] == '/'||expression[i]=='^') {
if (opt.empty() || opt.top() == '(') {
opt.push(expression[i]);
}
else {
while (!opt.empty() && opt_prior(opt.top()) >= opt_prior(expression[i])) {
node* n = new node;
stemp += opt.top();
n->s = stemp;
n->next = head->next;
head->next = n;
opt.pop();
stemp = "";
}
opt.push(expression[i]);
}
continue;
}
if (expression[i] == '(') {
opt.push(expression[i]);
continue;
}
if (expression[i] == ')') {
while (opt.top() != '(') {
stemp += opt.top();
node* n = new node;
n->s = stemp;
n->next = head->next;
head->next = n;
opt.pop();
stemp = "";
}
opt.pop();
continue;
}
if (isNumber(expression[i])) {
stemp += expression[i];
while (i < expression.size() - 1 && (isNumber(expression[i + 1])||expression[i+1]=='.')) {
stemp += expression[i + 1];
i++;
}
node* n = new node;
n->s = stemp;
n->next = head->next;
head->next = n;
}
if (expression[i] == 'e') {
stringstream stream;
stream << e;
stream >> stemp;
node* n = new node;
n->s = stemp;
n->next = head->next;
head->next = n;
}
if (expression[i] == 'p'){
stringstream stream;
stream << pi;
stream >> stemp;
node* n = new node;
n->s = stemp;
n->next = head->next;
head->next = n;
i++;
}
}
while (!opt.empty()) {
string stemp = "";
stemp += opt.top();
node* n = new node;
n->s = stemp;
n->next = head->next;
head->next = n;
opt.pop();
}
return head;
}
posfix_calculation()
首先创建一个栈用于存放运算数
后缀表达式计算遵循以下几个规则遍历后缀表达式即可
- 遍历字符如果是数字,则将该数压入栈中
- 遍历字符如果是运算符,则抛出栈中上面两个运算数根据运算符类型进行运算,并将运算结果压入栈中
- 遍历结束后栈顶元素即为运算结果
- 后缀表达式运算的c++实现:
double posfix_calculation(string *posfix, int num) {
stack<double> operand;
for (int i = 0; i < num; i++) {
if (isNumber(posfix[i][0])) {
double oper;
stringstream stream;
stream << posfix[i];
stream >> oper;
operand.push(oper);
}
if (posfix[i][0] == '+') {
double oper1, oper2;
oper1 = operand.top();
operand.pop();
oper2 = operand.top();
operand.pop();
operand.push(oper1 + oper2);
}
if (posfix[i][0] == '-') {
double oper1, oper2;
oper2 = operand.top();
operand.pop();
oper1 = operand.top();
operand.pop();
operand.push(oper1 - oper2);
}
if (posfix[i][0] == '*') {
double oper1, oper2;
oper1 = operand.top();
operand.pop();
oper2 = operand.top();
operand.pop();
operand.push(oper1 * oper2);
}
if (posfix[i][0] == '/') {
double oper1, oper2;
oper2 = operand.top();
operand.pop();
oper1 = operand.top();
operand.pop();
if (abs(oper2) < 1e-6) {
cout << "表达式中存在除数为0,无法计算.";
return 0.0;
}
operand.push(oper1 / oper2);
}
if (posfix[i][0] == '^') {
double oper1, oper2;
oper2 = operand.top();
operand.pop();
oper1 = operand.top();
operand.pop();
operand.push(pow(oper1, oper2));
}
}
return operand.top();
}
pretreat()
表达式字符串预处理首先是异常处理,剔除空格并检测表达式是否正确;
其次便是处理负数,处理负数算法步骤如下:
- 遍历字符串直到遇到 ’-‘
如果该 ’-‘ 位于字符串首位,则在字符串首位添加’0‘
如果该 ’-‘ 不是字符串首位且前一位字符是 ’(‘,则在 ’-‘ 前添加 ’0‘
其他情况视为减号 - 处理表达式中负号的c++实现:
for (int i = 0; i < expression.length(); i++) {
if (expression[i] == '-') {
if (i == 0) {
expression.insert(0, 1, '0');
continue;
}
if (expression[i - 1] == '(') {
expression.insert(i, 1, '0');
}
}
【函数调用关系图】
3.详细设计和编码
【详细说明主要数据结构和基本操作】
【代码(C++)】
4.测试结果及其分析
【测试案例】
【测试结果】
5.用户使用说明
6.参考文献
7.附录