数据结构课程设计

项目一:计算机设计大赛赛事统计

问题分析及任务定义

【问题描述】

参加计算机设计大赛的n个学校编号为1-n,赛事分成m个项目,项目的编号为1-m.比赛获奖按照得分降序,取前三名,写一个统计程序产生各种成绩单和得分报表。

【基本要求】
  1. 每个比赛项目至少有10支参赛队;每个学校最多有6支队伍参赛; 能统计各学校的总分;
  2. 可以按照学校编号或名称查询,学校的总分、各项目的总分排序输出;
  3. 可以按学校编号查询学校某个项目的获奖情况;
  4. 可以按项目编号查询取得前三名的学校;
  5. 数据存入文件并能随时查询

【设计要求】

  1. 输入数据形式和范围:可以输入学校的名称,赛事项目的名称。
  2. 输出形式:有中文提示,各学校分数为整数
  3. 界面要求:交互设计要合理,每个功能可以设菜单,根据提示,可以完成相关功能的要求。
  4. 存储结构:学生自己根据系统功能要求自己设计,但是赛事相关数据要存储在文件中。

数据结构的选择和概要设计

【抽象数据类型(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.附录

项目二:校园导游咨询

问题分析及任务定义

【问题描述】

设计一个校园导游程序,为来访的客人提供各种信息查询服务。

【基本要求】
  1. 设计你所在学校的校园平面图,所含景点不少于10个. 以图中顶点表示校内各景点,存放景点名称、代号、简介 等信息;以边表示路径,存放路径长度等相关信息。
  2. 为来访客人提供图中任意景点相关信息的查询。
  3. 为来访客人提供图中任意景点的问路查询,即查询任意两个景点之间的一条最短的简单路径。

数据结构的选择和概要设计

【抽象数据类型(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.附录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值