《数据结构》基于平衡二叉树的学生信息管理系统

 

程序源码在文章末尾

目  录

目  录... II

第1章 绪论... 3

1.树与二叉树的介绍... 3

2.二叉搜索树的定义以及优点... 3

3.平衡二叉树的介绍... 4

2章 运行结果展示... 5

第3章 平衡二叉树的程序实现... 11

1.结点、Student类和Tree类的构建... 11

2.删除算法deleteAVL的实现... 12

3.插入算法insertAVL. 14

4.通过调整使二叉树平衡的adjustBalance算法的实现... 14

4.1 四种旋转纠正算法的实现.. 14

4.2 adjustBalance算法的实现.. 15

第4章 运行类run类的实现... 16

4.1运行类Run类的构建... 17

4.2添加学生信息功能的实现... 17

4.3排序和输出学生信息功能的实现... 18

4.4通过成绩查找以及删除功能的实现... 19

第5章存在的不足与改进方案... 20

5.1关于基本平衡树无法储存相同数据方面的不足... 20

5.2关于单一树插入引用数据类型方面的不足... 20

5.3关于单一树构造时机方面的不足... 21

5.4区间查找算法的设想... 21

 

第1章 绪论

1.树与二叉树的介绍

树(Tree)是由n个结点构成的有限集合。在一棵树上,只有一个根节点(root)。每一个除了根节点以外,其他每个结点上边连接着另一个结点,下边又连接着若干个其他结点。把某一结点从树上取下来,被取下结点的下边相连的每一个结点看作新的根结点,每一个新的根及其下边的部分,又具有与树完全相同的结构,称为子树。

二叉树是一种特殊的树,每一个结点下边至多连接两个其他的结点,二叉树的名称也由此而来。与树类型,二叉树的每个节点下,都有子树,根据位置不同,我们可以定义为左子树和右子树(或者左孩子和右孩子)。

2.二叉搜索树的定义以及优点

二叉搜索树就是一个左子树的key值 < 本节点的key值 < 右子树的key值的二叉树。比如下图就是一个二叉搜索树。

31bc9cb7366949e385305cf74d3d4718.png

图1.1 二叉搜索树

二叉搜索树在插入时就需要判断key值的大小,使插入的数据能插入在满足上述条件的位置。插入数据的过程,就是构建二叉树的过程。删除操作同样需要使删除后的树满足上述条件。删除和插入操作的实现会在下文中有所提到,这里不再赘述。

如果通过中序遍历此树的话,就会的到从小到大的结果。对于使用者来说,从结果上看,如果把二叉搜索树看成一个数组的话,那么二叉搜索树就是一个可以实现插入时就完成排序的数组。

当然,排序不是二叉搜索树的核心,二叉搜索树也不会单独让用户随意地进行插入工作。二叉搜索树的主要功能从它的名字就可以看出——搜索。因为二叉搜索树左子树的key值 < 本节点的key值 < 右子树的key值,当待查找值num大于结点上的key值时,只需在右子树上查找,这样子就相当于实现了二分查找的操作,查找速度会远快于线性结构的查找。这也正是二叉搜索树的优点所在。

3.平衡二叉树的介绍

二叉平衡树的定义就是:任意节点的子树的高度差都小于等于 1。同时它还需要满足二叉搜索树需要满足的条件。可以把儿茶平衡树看成动态的二叉搜索树。

前文提到,二叉搜索树不能由用户随意地进行插入工作,对于给定的数组,需要有先后的插入顺序。比如如果直接插入{1,2,3,4,5,6}数组的话,那生成的树就会如下图所示

d5fc097581cb48ce913f4d0245d28087.png

图1.2 畸形的二叉搜索树

这样的二叉树就变得毫无意义。

但是实际应用中,插入数据的时机往往是认为难以预料的,而平衡二叉树恰恰能实现:每次插入数据后,都能使树平衡,这也是其名字的由来。

为了更好的实现平衡二叉树,这里引入平衡因子BF=左子树高度 - 右子树高度的值。一般来说 BF 的绝对值大于 1,,平衡树二叉树就失衡,需要通过旋转树来使二叉树平衡了。二叉树最多有两个结点,所以只存在左旋和右旋两种旋转方式。对于插入一个数据后造成二叉树不平衡的结果,可分为四类。即LL型,RR型,LR型和RL型,结构见下图:

6f92a426002143a1a09a8da63de4340f.png

 

图1.3 二叉树不平衡类型

对于上述四种不平衡的类型,只需要通过旋转,就可以使二叉树平衡,具体实现如下:

LL 型:插入左孩子的左子树,右旋

RR 型:插入右孩子的右子树,左旋

LR 型:插入左孩子的右子树,先左旋,再右旋

RL 型:插入右孩子的左子树,先右旋,再左旋

平衡二叉树的应用场景和二叉搜索树类似,主要还是排序和搜索,其搜索速度会远快于线性结构,当数据量较大时,这个速度差会变得更大。

2章 运行结果展示

开始运行程序,进入菜单1

4e764d097b1441c3a26207eeafef5f26.png

图2.1菜单1

输入2,调用排序并输出程序,在输入1,通过语文成绩排序如图

dcac14bfe5a144929bd9ea10ae509b9d.png

图2.2 按照语文成绩排序

同理测试通过数学、英语、总成绩排序结果如图

642052d385394cf6a9d1f333cfac357d.png

图2.3按照数学成绩排序

123b56e1d794410ebd4841cc5b7b63b8.png

图2.4 按照英语成绩排序

29f07e2d45a14f7f9b950671e5396135.png

图2.5 按照总成绩排序

运行结果无误。

在输入1,添加一个学生的信息

d4b1abdc593246fc9c6d518022d0ad88.png

图2.6 插入一个数据

重新调用2排序并输出,测试通过语文排序和数学排序,通过英语排序和总分排序与前两者类似,不再赘述。实验结果如图

f35e70a06477457abf684aca563f5256.png

图2.7插入后按照语文排序

39fa0977da3747bea47624ba618d0e35.png

图2.8插入后按照数学排序

运行结果无误。

输入4,通过学号查找,并输入211100,结果如图

4d5ad8804d594165821bfa4d11999594.png

图2.9 通过学号查找

输入5,通过姓名查找,并输入“成绩”,结果如图

85ef67fad9ed4f58ad4541288395ebd6.png

图2.10 通过姓名查找

输入3,输出学生信息,结果如图

4a11ceff868748f98a8b89df5647a4de.png

图2.11插入成绩后输出信息

 

输入7,通过成绩删除并输入86.1,删除王1的信息,结果如图

7496565d793149d1b3c06d507ac3616a.png

图2.22删除王1的信息

再输出3,输出学生信息

15bfee7365ec4027acf54ce68fbf69d0.png

图2.23输出删除王1信息后学生的信息

王1信息已被删除。

上述测试均与预期相符。

第3章 平衡二叉树的程序实现

1.结点、Student类和Tree类的构建

定义结点类型为Node,每个结点包含一个学生结构类型Student,指向其左右孩子的两个指向结点结构类型的指针,树的深度depth。代码如下图。

Student类内包含姓名name、学号id、语数英三门单科分数、总分数tot。还有提供了一个有参构造,用来初始化学生信息。还有一个用于输出写生信息的show_inf()函数。

c0ad80df56d74263bf688606e15655b3.png

图3.1结点类型的构建

90128c054184472a91a542e29dda2111.png

图3.2 Student类的构建

为了便于把学生按照某一科目的成绩插入平衡二叉树,特提供Loc_chec Loc_chec(LPNode* root,Student stu, int check) ;没有返回值,其作用是读取stu类和root里的stu类里check对应的成绩的值,分别赋给key和rot。这样就实现了读取不同科目的成绩了。为了适应Loc_chec函数,特意构建了空的学生类stunull。

Tree类里还含有包括删除、插入、查找等函数的实现。为了实现树的平衡,Tree类里还有实现了平衡二叉树的调整函数。Tree的定义如下图。

a932d8ebf98b475e8d8add3047481653.png

图3.3 Tree类的实现

2.删除算法deleteAVL的实现

      关键词key的值与根结点的对应值对比,若等于即删除该节点,若不等则向下查找(key较大向右找,key较小向左找),直至找到对应值与key相等的结点,对其进行删除。

删除时分无子、有左孩子、有右孩子、有两子四种情况讨论。无子直接将改结点的指针置NULL,有左孩子则用其左孩子的指针覆盖该结点的指针,右孩子类似,有两子则用其左孩子指针覆盖该结点的指针、并将其右孩子连接到要删结点的左孩子树上(作为左孩子的最后一个右孩子的右孩子)。

执行此操作前,需要先确定结点上的stu是通过哪种方式插入树的,因此需要调用Loc_chec函数,来读取stu类内和结点上stu类内与check对应的科目的值,便于比较。

执行此操作后,需要调用adjustBalance函数,使二叉树平衡。

此过程的实现需要用到递归操作

e3b536821b614ed4b6732692c1ac9b79.png


代码如下图。

图3.4 删除算法的实现

 

3.插入算法insertAVL

插入的结点一定在树的最底层,故该结点为空,需要重新创建,该结点的深度为1,左右孩子指针赋FULL。

插入过程需要将关键词key的值与根结点的对应值对比,若key较大则向右找,key较小向左找,直至找到空节点为止。

调用此算法后需要通过adjustBalance函数,使二叉树平衡。代码如下图。

30053e89eefc4164acd4c751ef8c19f2.png

图3.5 插入算法的实现

4.通过调整使二叉树平衡的adjustBalance算法的实现

4.1 四种旋转纠正算法的实现

四种旋转纠正算法也就是LL型旋转、RR型旋转、LR型旋转、RL型旋转。四种类型的旋转中,知识左旋和右旋的先后次序不同,对于RR型旋转,还需要重新计算树的深度。这四个旋转算法,是实现可以使二叉树平衡的adjustBalance算法实现的前提条件。LL进行一次右旋(执行LL_Rotation):cache指向根结点的左子树,cache的右子树挂接为原根节点的左子树。RR进行一次左旋(执行RR_Rotation): cache指向根结点的右子树,cache的左子树挂接为原根节点的右子树。LR先左旋再右旋(先RR_Rotation再LL_Rotation)。RL先右旋再左旋(先LL_Rotation再RR_Rotation)。

代码实现如下图

5b7617aae28d443f9e4b8908bf0c9ce1.png0dea5fdde18f46d7ae219c60b00565c3.png

476ed12aac564d0f975d5d8ffdd1e9b3.png797f3b33d3e540a1b76ccd1128b1977e.png

图3.6 四种旋转算法的实现

4.2 adjustBalance算法的实现

取根结点左右孩子的深度进行对比,若差大于1,则树不平衡。

不平衡的树需进行旋转操作的排序树分为四种,LL型——因左孩子深度更大而不平衡,插入的结点作为左孩子;LR型——因左孩子深度更大而不平衡,插入的结点作为右孩子;RL——因右孩子深度更大而不平衡,插入的结点作为左孩子;RR——因右孩子深度更大而不平衡,插入的结点作为右孩子。

对这四种类型的树分别进行不同的旋转操作,将其调整为平衡树。

具体代码如下图

4b173cd0246c48db97bad2f8068b07c6.png

图3.6 平衡算法的实现

第4章 运行类run类的实现

Run类就是控制程序运行的类,是程序与用户交互的窗口,main函数中通过调用Run类的一个对象run来实现菜单上的各种功能,菜单如下图4.1。功能3输出学生成绩、功能6按照成绩查找和功能7按照成绩删除,都是在功能2排序并输出成绩的基础上才能实现的,因此需要先调用功能2,功能3、6、7才能实现。功能1是在学生数组stuArr内添加新的成员,而功能2的就是把学生数组内的所有学生,依据某一科的成绩或总成绩,把学生“挂”在平衡树上的结点上。

0cd79c7c9c5948429ab984c9896e9e4e.png

图4.1菜单演示

4.1运行类Run类的构建

Run类包含一个Tree,一个结点指针类型的指针root,用于记录当前学生数量的int型数据num,以及用于暂存id等数据的变量。通过private封装在Run类内。如图4.2.

1e085f10eefc4922826687547dc75477.png

图4.2,Run类的变量

此外,Run类内还包含无参构造函数,用于初始化一些数据,方便测试与使用;用于显示菜单的show_menu函数,为实现功能1添加学生信息而构建的添加学生信息的stuArr_creat函数;为实现功能2排序并输出而构建的showAs函数;为实现功能3显示成绩而构建的show_sco函数;通过id查找和通过姓名查找的check_id和check_name函数;为实现功能6按成绩查找而构建的search函数和为实现功能7按照成绩删除而构建的dele函数。如图4.3所示

d3aa95ab2743411c91f6c65c04114d37.png

图4.3Run类内的函数

4.2添加学生信息功能的实现

stuArr是一个具有学生类类型的数组,添加学生信息,就是在stuArr数组内添加学生信息。

添加信息的第一步是判断stuArr内是否有数据,也即学生人数num是否为0。如果有,也就是num>0,则需要先把stuArr内的数据存到new_stuArr数组内,在重新给stuArr分配一个更大的空间,最后把new_stuArr内记录的一开始在stuArr内的数据重新存到新的stuArr内。然后添加学生信息只需要在stuArr数组内第num位后添加学生信息即可。

如果stuArr内一开始没有数据,也就是num=0则只需给stuArr开辟空间,再在stuArr内添加学生信息即可。

代码实现如下图4.4

b9d57655ff564ab2a19e301191b31b35.png

图4.4添加学生信息功能的实现

4.3排序和输出学生信息功能的实现

排序就是把stuArr数组内所有学生的信息,根据某一科目成绩或总分的高低,来把学生“挂”在平衡二叉树的结点上。因此排序函数showAs需要传入一个int型的参数check,来记录需要用哪一科目的成绩来进行排序,check等于1、2、3、4,依次对用通过语文、数学、英语、总分排序。

排序后,只需通过中序遍历,遍历平衡二叉树并调用每一个结点上的student类型的数据的show_inf()函数,就可以显示结点上存储的学生的信息了。

代码实现如下图4.5

793159264cfc406bb8edf88026954826.png

图4.5排序和输出功能的实现

4.4通过成绩查找以及删除功能的实现

查找和删除都是在平衡二叉树上实现的,因此,通过成绩查找和通过成绩查找并删除功能的实现,只需分别调用平衡二叉树的search功能和delete功能即可实现。

在排序时会用一个int类型数据sort记录排序方式,也就是通过sort对应的科目进行排序。在查找时,只能通过sort对应的科目进行查找,因为树是以sort对应的科目的成绩生成的。

代码实现如图4.6

93974d82c8284fb29c9974b7cb52a0d6.png

图4.6通过成绩查找以及删除功能的实现

第5章存在的不足与改进方案

5.1关于基本平衡树无法储存相同数据方面的不足

平衡二叉树上可以实现插入时即排序,在查找时的速度远快于线性存储结构。但是正是因为平衡二叉树插入时就需要排序,使得平衡二叉树无法存储相同的数据——平衡二叉树左树上的key值总是“小于”根节点的key值,右树上的key值总是“大于”根节点的key值,不存在key值相等的情况。如果尝试插入相同的数据,会插入失败。

本项目是基于平衡二叉树实现学生成绩管理的,学生成绩难免会出现某一科成绩相同的情况,这也就意味着本项目的应用场景会远小于预期。

为解决此方面的不足,可以尝试使用STL容器中的multiset容器或者multiset容器中的部分底层算法。multiset容器的底层也是二叉树,在插入数据时即排序,搜索速度更快,除此之外,multiset容器也实现了允许插入相同的数据的功能。

5.2关于单一树插入引用数据类型方面的不足

平衡二叉树插入数据时,key值需要有一个衡量大小的标准。本项目的平衡二叉树上的key值为student类,衡量的student大小的标准就是某一科目的成绩的高低,具体是哪一科目可以由用户指定。

本项目只构建了一棵平衡二叉树,必须先调用功能2,确定通过哪一个科目的成绩排序后,才会构建平衡二叉树。

因此,在通过某一科成绩把student挂在二叉树上的结点后,能且仅能通过那一个科目的成绩进行查找操作。对于本项目来说,“通过哪一个科目的成绩排序,就只能通过那一个科目的成绩进行查找”是远远不够的。

为解决此问题,可以尝试创建四棵平衡二叉树,分别以三个科目的成绩的高低以及总分的高低为衡量student的标准来构建。这样,在查找时,只需先找到对应的树,就可以实现用通过不同科目的成绩来进行查找了。

5.3关于单一树构造时机方面的不足

本项目只有一个平衡二叉树,而且需要用户指定排序方式后才能构建平衡二叉树,也就是调用功能2之后才会构建平衡二叉树。然而输出学生成绩和按照成绩查找,都是在已构建平衡二叉树之后才能实现的。也就是说,在用户调用功能2之前,功能3、6、7都不能使用。

此问题的解决方法与5.2的解决方法类似,如果一开始就创建了四颗平衡二叉树,在用户指定按照某种方式排序前,四颗树就可以按照不同科目的成绩进行插入操作,当用户指定按照某种方式排序并输出时,只需输出对应的树即可。这样就可以实现了一边插一入边构建二叉树的操作。

5.4区间查找算法的设想

相比于“查找成绩在某个区间的同学的信息”,“通过成绩查找某个学生的信息”显得十分狭隘了,应用场景也不如前者。

平衡二叉树最大的有点就是查找速度快,但在区间查找方面仍存在许多问题:比如:找到区间极大值极小值的位置后,如何返回这两个结点的父树结点的值。

为了解决上述问题,可以尝试在结点里加入一个指向父亲结点的指针,这样就可以实现区间查找算法了。具体的实现可能还会遇到更多问题。

 

程序源码:

main.cpp文件

#include<iostream>
#include"Tree.h"
#include"Student.h"
#include<string>
using namespace std;
class Run {
public:
	Run() {//无参构造,初始化6个学生的信息,便于测试
		this->num = 6;
		stuArr = new Student[6];
		stuArr[0] = Student(21101, "王1", 71.1, 86.1, 92.1);//数据依次为:学号 姓名 语文 数学 英语,总分自动计算
		stuArr[1] = Student(21102, "王2", 73.1, 85.1, 94.2);
		stuArr[2] = Student(21103, "王3", 72.1, 84.1, 99.1);
		stuArr[3] = Student(21104, "王4", 76.1, 83.1, 95.1);
		stuArr[4] = Student(21105, "王5", 75.1, 82.1, 94.1);
		stuArr[5] = Student(21106, "王6", 77.1, 81.1, 96.1);
	}
	void show_menu1() {//菜单1,显示主菜单
		cout << "---------欢迎使用学生成绩管理系统---------" << endl;
		cout << "\t|-****1.添加学生信息****-|" << endl;
		cout << "\t|-****2.排序并输出成绩**-|" << endl;
		cout << "\t|-****3.输出学生成绩****-|" << endl;
		cout << "\t|-****4.通过学号查找****-|" << endl;
		cout << "\t|-****5.通过姓名查找****-|" << endl;
		cout << "\t|-****6.按照成绩查找****-|" << endl;
		cout << "\t|-****7.按照成绩删除****-|" << endl;
		cout << "\t|-****0.退出程序********-|" << endl;
		cout << "\t-------------------------" << endl;
	}
	void show_menu2() {//菜单2,显示排序并输出菜单
		cout << "-****1.按照语文成绩排序后输出学生成绩*-" << endl << endl;
		cout << "-****2.按照数学成绩排序后输出学生成绩*-" << endl << endl;
		cout << "-****3.按照英语成绩排序后输出学生成绩*-" << endl << endl;
		cout << "-****4.按照总成绩排序后输出学生成绩***-" << endl << endl;
	}
	void stuArry_creat() {//添加新的学生的成绩信息
		int n;
		cout << "请输入要添加的人的人数:";
		cin >> n;
		if (n <= 0) {//添加人数小于0,输入值无效
			cout << "输入的人数无效";
			return;
		}
		
		if (num == 0) {//如果插入时,一个学生的成绩都没录入
			num = n;
		}
		else {//如果插入时,stuArr里已经有数据了(num>0),则给stuArr开辟一个更大的区间
				//并记录下一开始就在stuArr里的数据
			auto new_stuArr = new Student[num + n];//开辟空间以暂存一开始就在stuArr里的数据
			for (int i = 0; i < num; i++) {
				new_stuArr[i] = stuArr[i];
			}
			stuArr = new Student[num + n];
			for (int i = 0; i < num; i++) {//记录一开始的就在stuArr里的数据
				stuArr[i] = new_stuArr[i];
			}
			this->num = num + n;
		}
		for (int i = 0; i < n; i++) {//输入数据
			cout << "请输入第" << i + 1 << "个人的信息\t\n";
			cout << "学号:"; cin >> this->id;
			cout << "姓名:"; cin >> this->name;
			cout << "语文成绩:"; cin >> this->chine;
			cout << "数学成绩:"; cin >> this->match;
			cout << "英语成绩:"; cin >> this->engli;
			stuArr[i + num-1] =Student(id, name, chine, match, engli);
		}
	}
	void showAs(int check) {//按照第i种方式排序。i大于0小于等于4
		/*在把数据挂到树上前,先把树清空*/
		if (tree) {
			tree = nullptr;
			delete tree;
			tree = new Tree;
		}
		if (root) {
			root = nullptr;
			delete root;
			root = NULL;
		}
		switch (check) {
		case 1: {
			cout << "按照语文高低排序的结果为:" << endl;
			break;
		}
		case 2: {
			cout << "按照数学高低排序的结果为:" << endl;
			break;
		}
		case 3: {
			cout << "按照英语高低排序的结果为:" << endl;
			break;
		}
		case 4: {
			cout << "按照总分高低排序的结果为:" << endl;
			break;
		}
		}

		for (int j = 0; j < num; j++) {
			//插入数据,参数依次为根,student类数据,排序方式。排序方式的1 2 3 4依次对应通过语数外总成绩插入。
			tree->insertAVL(&root, stuArr[j],check);										
			//cout << 1 << endl;//检测异常发生的位置
		}
		tree->midOrder(root);//中序遍历数,输出学生信息
		cout << endl;
	}
	void show_sco() {
		tree->midOrder(root);
	}
	bool check_id(int id) {//通过id查找
		for (int i = 0; i < this->num; i++) {
			if (stuArr[i].id == id) {
				stuArr[i].show_inf();
				cout << endl;
				return true;
			}
		}
		return false;
	}
	bool check_name(string name) {//通过姓名查找
		for (int i = 0; i < this->num; i++) {
			if (stuArr[i].name == name) {
				stuArr[i].show_inf();
				cout << endl;
				return true;
			}
		}
		return false;
	}
	void search(int check,double nu) {//通过成绩查找,输入参数依次为查找方式,待查找的成绩值
		if (tree->search(root, check, nu)) {
			tree->search(root, check, nu)->stu.show_inf();//search()为在平衡树上查找的函数,返回查找到的结点或差找不到时返回NULL
			cout << "查找成功";
		}
		else {
			cout << "查找失败,请检查成绩是否有误";
		}
		
	}
	void delet(int check,double nu) {
		if (tree->deleteAVL(&root, nu, check)) {
			cout << "删除成功"<<endl;
			cout << "删除之后的结果为:"<<endl;
			this->num--;
			tree->midOrder(root);
		}
		else {
			cout << "查找失败,请检查成绩是否有误";
		}
		
	}
private:
	Tree* tree = new Tree;//平衡二叉树
	LPNode root = NULL;//跟结点
	int num=0;//当前学生的数量
	Student* stuArr;//学生数组,用于暂时记录学生成绩信息
	int id;//暂时记录一个id
	string name;//暂时记录一个id,便于输入输出,无实际意义。下同
	double chine;
	double match;
	double engli;
};
int main()
{

	Run run;
	int now=1;//确定当前程序需要执行那一条
	int check;//确定查找方式
	int sort=1;//确定排序方式
	while (1) {
		run.show_menu1();//显示菜单1
		cout << "请输入对应功能的编号:"; cin >> now;
		switch (now) {
		case 1: {
			run.stuArry_creat();
			cout << "录入成功";
			break;
		}
		case 2: {//排序并输出成绩
			run.show_menu2();//显示菜单2
			cout << "输出学生成绩前,请选择排序方式:";
			cin >> check;
			switch (check) {
			case 1: {//按照语文排序
				run.showAs(1);
				sort = 1; break;
			}
			case 2: {//按照数学排序
				run.showAs(2); 
				sort = 2; break;
			}
			case 3: {//按照英语排序
				run.showAs(3); 
				sort = 3; break;
			}
			case 4: {//按照总分排序
				run.showAs(4); 
				sort = 4; break;
			}
			default: {
				cout << "无效的指令,请重新输入" << endl;
				cin.clear();
				cin.ignore(numeric_limits<std::streamsize>::max(), '\n');
				cin.ignore();
			}
			}
			break;
		}
		case 3: {
			run.show_sco();
			break;
		}
		case 4: {//通过学号查找
			cout << "请输入待查找学生的学号:";
			int id;
			cin >> id;
			if (run.check_id(id)) {
				cout << "查找成功";
			}
			else {
				cout << "查找失败";
			}
			break;

		}
		case 5: {//通过姓名查找
			cout << "请输入待查找学生的姓名:";
			string name;
			cin >> name;
			if (run.check_name(name)) {
				cout << "查找成功";
			}
			else {
				cout << "查找失败";
			}
			break;
		}
		case 6: {//按照成绩查找
			switch (sort) {
			case 1: {//按照语文成绩查找
				cout << "请输入待查找学习的语文成绩:"; break;
			}
			case 2: {//按照数学成绩查找
				cout << "请输入待查找学习的语文成绩:"; break;
			}
			case 3: {//按照英语成绩查找
				cout << "请输入待查找学习的语文成绩:"; break;
			}
			case 4: {//按照总成绩查找
				cout << "请输入待查找学习的语文成绩:"; break;
			}
			}
			double nu; cin >> nu;
			run.search(4, nu); break;
			break;
		}
		case 7: {
			/*run.show_menu4();
			cout << "删除学生信息,请检索待删除学生信息的方式:";*/
			//cin >> check;
			switch (sort) {//sort取值为1 2 3 4,sort表示当前树里的排序方式,有case2确定
			case 1: {
				cout << "请输入待删除学生的语文成绩:"; break;
			}
			case 2: {
				cout << "请输入待删除学生的数学成绩:"; break;
			}
			case 3: {
				cout << "请输入待删除学生的英语成绩:"; break;
			}
			case 4: {
				cout << "请输入待删除学生的总成绩:"; break;
			}
			}
			double nu;
			cin >> nu;
			run.delet(sort, nu);
			break;
			
			
		}
		case 0: {//退出程序
			cout << "欢迎下次使用" << endl;
			exit(0);
		}
		default: {
			cout << "无效的指令,请重新输入" << endl;
			cin.clear();//重新给cin置为并清空缓存,避免当cin溢出时,不再接收下次的输入了
			cin.ignore(numeric_limits<streamsize>::max(), '\n');
		}
		}
		system("pause");
		system("cls");
	}
}

student.cpp文件

#include"Student.h"

//带参构造,参数依次为id,姓名,语文成绩,数学成绩,英语成绩。。总成绩自动计算
Student::Student(int id, string name, double chi, double mat, double eng) {
	this->id = id;
	this->chi = chi;
	this->mat = mat;
	this->eng = eng;
	this->tot = chi + mat + eng;
	this->name = name;
}

void Student::show_inf() {//输出stu里的信息,包括id姓名和各科成绩
	cout << this->id << "\t" << this->name << "\t语文成绩:" <<this->chi<< "\t数学成绩:"<<this->mat 
		<< "\t英语成绩:"<<this->eng << "\t总成绩:"<<this->tot;
	cout << endl;
}

Tree.cpp文件

#include"Tree.h"

bool Tree::deleteAVL(LPNode* root, double nu, int check)//删除
{

	if (*root == NULL)
		return false;
	Loc_chec(root, stunull, check);
	if (nu == rot)//值相等说明找到了
	{
		LPNode q, s;
		//根据该结点左右孩子是否为空,确定如何删除该结点
		if ((*root)->left == NULL && (*root)->right == NULL){
			q = (*root);//子树都为空,直接删除
			(*root) = NULL;
			delete q;
		}
		else if ((*root)->left == NULL){
			q = (*root);//左空右不空,把右子树的根节点挂在待删除的结点上
			*root = (*root)->right;
			delete q;
		}
		else if ((*root)->right == NULL){
			q = *root;//右空左不空,把左子树的根节点挂在待删除的结点上
			*root = (*root)->left;
			delete q;
		}
		else{//左右子树都不为空
			q = *root;
			s = (*root)->left;
			while (s->right){
				q = s;
				s = s->right;
			}
			(*root)->stu = s->stu;
			if (q != *root){
				q->right = s->left;
			}
			else{
				q->left = s->left;
			}
		}
		return true;
	}
	else if (nu > rot)
	{
		switch (check) {
		case 1: {
			stunull.chi = nu;
		}
		case 2: {
			stunull.mat = nu;
		}
		case 3: {
			stunull.eng = nu;
		}
		case 4: {
			stunull.tot = nu;
		}
		}
		//如果本次传进去删掉了(*root)->right,那么return回来就是true,则需要调整(*root)
		//否则没有删掉,返回回来是false,则不需要调整
		auto flag = deleteAVL(&(*root)->right, nu,check);
		if (flag){
			//更新节点深度
			renewTreeDepth(*root);
			//二叉树平衡的调整
			adjustBalance(root, stunull, check);
		}
		return flag;
	}
	else
	{
		//如果本次传进去删掉了(*root)->left,那么return回来就是true,则需要调整(*root)
		//否则没有删掉,返回回来是false,则不需要调整
		auto flag = deleteAVL(&(*root)->left, nu,check);
		if (flag){
			//更新节点深度
			renewTreeDepth(*root);
			//二叉树平衡的调整
			adjustBalance(root, stunull, check);
		}
		return flag;
	}
}
void Tree::insertAVL(LPNode* root, Student stu,int check)//插入
{

	if (*root == NULL){//为空,则新建
		//(*root) = (LPNode)malloc(sizeof(struct Node));
		(*root) = new Node;
		if (*root == NULL) return; 
		(*root)->stu = stu;
		(*root)->depth = 1; 
		(*root)->left = NULL;
		(*root)->right = NULL; 
	}
	else{//不为空,判断与结点值的关系,大了往右,小了往左
		Loc_chec(root, stu, check);
		if (key < rot){
			insertAVL(&(*root)->left, stu, check);
		}
		else if (key > rot){
			insertAVL(&(*root)->right, stu, check);
		}
	}
	//更新节点的深度
	renewTreeDepth(*root);

	//二叉树平衡的调整
	adjustBalance(root, stu, check);
}

void Tree::midOrder(LPNode root)//中序遍历
{
	if (root != NULL){
		midOrder(root->left);//左
		root->stu.show_inf();//调用stu的输出信息函数,输出stu的信息
		midOrder(root->right);//右
	}
}

LPNode Tree::search(LPNode root,int check,double nu)//查找
{
	if (root)
	{
		Loc_chec(&root, stunull, check);
		if (nu >rot){//待查找值比结点值大,往右子树找
			search(root->right, check,nu);
		}
		else if (nu < rot){//待查找值比结点值小,往左子树找
			search(root->left, check, nu);
		}
		else if(nu==rot){//找到了
			return root;
		}
	}
	else {//找不到
		return nullptr;
	}

}

int Tree::max(int a, int b)
{
	return a > b ? a : b;
}

int Tree::getDepth(LPNode node)
{
	if (node == NULL)
		return 0;
	return node->depth;
}

void Tree::adjustBalance(LPNode* root, Student stu, int check) {//调整

	if (getDepth((*root)->left) - getDepth((*root)->right) > 1) {
		Loc_chec(&(*root)->left, stu, check);
		if (key < rot) {
			//LL型
			LL_Rotation(root);
		}
		else {
			//LR型
			LR_Rotation(root);
		}
	}
	else if (getDepth((*root)->left) - getDepth((*root)->right) < -1) {
		Loc_chec(&(*root)->right, stu, check);
		if (key > rot) {
			//RR型
			RR_Rotation(root);
		}
		else {
			//RL型
			RL_Rotation(root);
		}
	}
}

void Tree::renewTreeDepth(LPNode root)//更新树的深度
{
	//必须写成函数,不然会传进来空指针,
	//比如:int leftDepth = root->left->depth;
	int leftDepth = getDepth(root->left);
	int rightDepth = getDepth(root->right);
	root->depth = max(leftDepth, rightDepth) + 1;
}

void Tree::LL_Rotation(LPNode* root)
{
	//LL型要右旋
	LPNode cache = (*root)->left;
	(*root)->left = cache->right;
	cache->right = (*root);
	renewTreeDepth(*root);//更新调整后的高度
	renewTreeDepth(cache);//更新root的指向
	*root = cache;
}

void Tree::RR_Rotation(LPNode* root)
{
	//RR型要左旋
	LPNode cache = (*root)->right;
	(*root)->right = cache->left;
	cache->left = (*root);
	//更新调整后的高度
	renewTreeDepth(*root);
	renewTreeDepth(cache);
	//更新root的指向
	*root = cache;
}

void Tree::LR_Rotation(LPNode* root)
{
	//LR型先左旋再右旋
	RR_Rotation(&(*root)->left);
	LL_Rotation(root);
}

void Tree::RL_Rotation(LPNode* root)
{
	//RL型,先右旋再左旋
	LL_Rotation(&(*root)->right);
	RR_Rotation(root);
}
//check等于1/2/3/4时,获取stu里和root里语文/数学/英语/总成绩,分别存到key和rot内。
//便于通过stu的不同成绩插入树时的比较操作
void Tree::Loc_chec(LPNode* root,Student stu, int check) {
	if (root) {
		switch (check) {
		case 1: {
			this->key = stu.chi;
			this->rot = (*root)->stu.chi;
			break;
		}
		case 2: {
			this->key = stu.mat;
			this->rot = (*root)->stu.mat;
			break;
		}
		case 3: {
			this->key = stu.eng;
			this->rot= (*root)->stu.eng;
			break;
		}
		case 4: {
			this->key = stu.tot;
			this->rot = (*root)->stu.tot;
			break;
		}
		default: {
			this->key = 10;
			this->rot = 10;
			break;
		}
		}
	}
	
}

Student.h

#pragma once
#include<iostream>
#include<string>
using namespace std;
class Student {
public:
	//带参构造,参数依次为id,姓名,语文成绩,数学成绩,英语成绩。。总成绩自动计算
	Student(int id, string name, double chi, double mat, double eng);
	Student() {};//无参空构造,
	void show_inf();//输出stu里的信息,包括id姓名和各科成绩

	double chi;//语文成绩
	double mat;//数学成绩
	double eng;//英语成绩
	double tot;//总成绩,总成绩=语文+数学+英语,自动计算生成
	int id;//学号
	string name;//姓名
};

Tree.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<string>
#include"Student.h"

typedef struct Node {
	Student stu = Student();
	struct Node* left;
	struct Node* right;
	int depth;
}*LPNode;

class Tree {
public:

	bool deleteAVL(LPNode* root, double nu,int check);//删除

	void insertAVL(LPNode* root, Student stu, int check);//插入

	void midOrder(LPNode root);//中序遍历

	LPNode search(LPNode root,int check,double nu);//查找

private:
	int max(int a, int b);

	int getDepth(LPNode node);//获取树的深度

	void adjustBalance(LPNode* root, Student stu, int check);//调整

	void renewTreeDepth(LPNode root);//更新树的深度

	void LL_Rotation(LPNode* root);//LL型旋转

	void RR_Rotation(LPNode* root);//RR型旋转

	void LR_Rotation(LPNode* root);//LR型旋转

	void RL_Rotation(LPNode* root);//RL型旋转

	//check等于1/2/3/4时,获取stu里和root里语文/数学/英语/总成绩,分别存到key和rot内
	void Loc_chec(LPNode* root,Student stu,int check);

	Student stunull = Student(1, "1", 1, 1, 1);//构建一个空的stu结点,方便Loc_hec()函数调用没实际作用
	
	double key = 1;

	double rot = 1;
	//LPNode* root;
};

 

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值