第1章 绪论
1.树与二叉树的介绍
树(Tree)是由n个结点构成的有限集合。在一棵树上,只有一个根节点(root)。每一个除了根节点以外,其他每个结点上边连接着另一个结点,下边又连接着若干个其他结点。把某一结点从树上取下来,被取下结点的下边相连的每一个结点看作新的根结点,每一个新的根及其下边的部分,又具有与树完全相同的结构,称为子树。
二叉树是一种特殊的树,每一个结点下边至多连接两个其他的结点,二叉树的名称也由此而来。与树类型,二叉树的每个节点下,都有子树,根据位置不同,我们可以定义为左子树和右子树(或者左孩子和右孩子)。
2.二叉搜索树的定义以及优点
二叉搜索树就是一个左子树的key值 < 本节点的key值 < 右子树的key值的二叉树。比如下图就是一个二叉搜索树。
图1.1 二叉搜索树
二叉搜索树在插入时就需要判断key值的大小,使插入的数据能插入在满足上述条件的位置。插入数据的过程,就是构建二叉树的过程。删除操作同样需要使删除后的树满足上述条件。删除和插入操作的实现会在下文中有所提到,这里不再赘述。
如果通过中序遍历此树的话,就会的到从小到大的结果。对于使用者来说,从结果上看,如果把二叉搜索树看成一个数组的话,那么二叉搜索树就是一个可以实现插入时就完成排序的数组。
当然,排序不是二叉搜索树的核心,二叉搜索树也不会单独让用户随意地进行插入工作。二叉搜索树的主要功能从它的名字就可以看出——搜索。因为二叉搜索树左子树的key值 < 本节点的key值 < 右子树的key值,当待查找值num大于结点上的key值时,只需在右子树上查找,这样子就相当于实现了二分查找的操作,查找速度会远快于线性结构的查找。这也正是二叉搜索树的优点所在。
3.平衡二叉树的介绍
二叉平衡树的定义就是:任意节点的子树的高度差都小于等于 1。同时它还需要满足二叉搜索树需要满足的条件。可以把儿茶平衡树看成动态的二叉搜索树。
前文提到,二叉搜索树不能由用户随意地进行插入工作,对于给定的数组,需要有先后的插入顺序。比如如果直接插入{1,2,3,4,5,6}数组的话,那生成的树就会如下图所示
图1.2 畸形的二叉搜索树
这样的二叉树就变得毫无意义。
但是实际应用中,插入数据的时机往往是认为难以预料的,而平衡二叉树恰恰能实现:每次插入数据后,都能使树平衡,这也是其名字的由来。
为了更好的实现平衡二叉树,这里引入平衡因子BF=左子树高度 - 右子树高度的值。一般来说 BF 的绝对值大于 1,,平衡树二叉树就失衡,需要通过旋转树来使二叉树平衡了。二叉树最多有两个结点,所以只存在左旋和右旋两种旋转方式。对于插入一个数据后造成二叉树不平衡的结果,可分为四类。即LL型,RR型,LR型和RL型,结构见下图:
图1.3 二叉树不平衡类型
对于上述四种不平衡的类型,只需要通过旋转,就可以使二叉树平衡,具体实现如下:
LL 型:插入左孩子的左子树,右旋
RR 型:插入右孩子的右子树,左旋
LR 型:插入左孩子的右子树,先左旋,再右旋
RL 型:插入右孩子的左子树,先右旋,再左旋
平衡二叉树的应用场景和二叉搜索树类似,主要还是排序和搜索,其搜索速度会远快于线性结构,当数据量较大时,这个速度差会变得更大。
第2章 运行结果展示
开始运行程序,进入菜单1
图2.1菜单1
输入2,调用排序并输出程序,在输入1,通过语文成绩排序如图
图2.2 按照语文成绩排序
同理测试通过数学、英语、总成绩排序结果如图
图2.3按照数学成绩排序
图2.4 按照英语成绩排序
图2.5 按照总成绩排序
运行结果无误。
在输入1,添加一个学生的信息
图2.6 插入一个数据
重新调用2排序并输出,测试通过语文排序和数学排序,通过英语排序和总分排序与前两者类似,不再赘述。实验结果如图
图2.7插入后按照语文排序
图2.8插入后按照数学排序
运行结果无误。
输入4,通过学号查找,并输入211100,结果如图
图2.9 通过学号查找
输入5,通过姓名查找,并输入“成绩”,结果如图
图2.10 通过姓名查找
输入3,输出学生信息,结果如图
图2.11插入成绩后输出信息
输入7,通过成绩删除并输入86.1,删除王1的信息,结果如图
图2.22删除王1的信息
再输出3,输出学生信息
图2.23输出删除王1信息后学生的信息
王1信息已被删除。
上述测试均与预期相符。
第3章 平衡二叉树的程序实现
1.结点、Student类和Tree类的构建
定义结点类型为Node,每个结点包含一个学生结构类型Student,指向其左右孩子的两个指向结点结构类型的指针,树的深度depth。代码如下图。
Student类内包含姓名name、学号id、语数英三门单科分数、总分数tot。还有提供了一个有参构造,用来初始化学生信息。还有一个用于输出写生信息的show_inf()函数。
图3.1结点类型的构建
图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的定义如下图。
图3.3 Tree类的实现
2.删除算法deleteAVL的实现
关键词key的值与根结点的对应值对比,若等于即删除该节点,若不等则向下查找(key较大向右找,key较小向左找),直至找到对应值与key相等的结点,对其进行删除。
删除时分无子、有左孩子、有右孩子、有两子四种情况讨论。无子直接将改结点的指针置NULL,有左孩子则用其左孩子的指针覆盖该结点的指针,右孩子类似,有两子则用其左孩子指针覆盖该结点的指针、并将其右孩子连接到要删结点的左孩子树上(作为左孩子的最后一个右孩子的右孩子)。
执行此操作前,需要先确定结点上的stu是通过哪种方式插入树的,因此需要调用Loc_chec函数,来读取stu类内和结点上stu类内与check对应的科目的值,便于比较。
执行此操作后,需要调用adjustBalance函数,使二叉树平衡。
此过程的实现需要用到递归操作
代码如下图。
图3.4 删除算法的实现
3.插入算法insertAVL
插入的结点一定在树的最底层,故该结点为空,需要重新创建,该结点的深度为1,左右孩子指针赋FULL。
插入过程需要将关键词key的值与根结点的对应值对比,若key较大则向右找,key较小向左找,直至找到空节点为止。
调用此算法后需要通过adjustBalance函数,使二叉树平衡。代码如下图。
图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)。
代码实现如下图
图3.6 四种旋转算法的实现
4.2 adjustBalance算法的实现
取根结点左右孩子的深度进行对比,若差大于1,则树不平衡。
不平衡的树需进行旋转操作的排序树分为四种,LL型——因左孩子深度更大而不平衡,插入的结点作为左孩子;LR型——因左孩子深度更大而不平衡,插入的结点作为右孩子;RL——因右孩子深度更大而不平衡,插入的结点作为左孩子;RR——因右孩子深度更大而不平衡,插入的结点作为右孩子。
对这四种类型的树分别进行不同的旋转操作,将其调整为平衡树。
具体代码如下图
图3.6 平衡算法的实现
第4章 运行类run类的实现
Run类就是控制程序运行的类,是程序与用户交互的窗口,main函数中通过调用Run类的一个对象run来实现菜单上的各种功能,菜单如下图4.1。功能3输出学生成绩、功能6按照成绩查找和功能7按照成绩删除,都是在功能2排序并输出成绩的基础上才能实现的,因此需要先调用功能2,功能3、6、7才能实现。功能1是在学生数组stuArr内添加新的成员,而功能2的就是把学生数组内的所有学生,依据某一科的成绩或总成绩,把学生“挂”在平衡树上的结点上。
图4.1菜单演示
4.1运行类Run类的构建
Run类包含一个Tree,一个结点指针类型的指针root,用于记录当前学生数量的int型数据num,以及用于暂存id等数据的变量。通过private封装在Run类内。如图4.2.
图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所示
图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
图4.4添加学生信息功能的实现
4.3排序和输出学生信息功能的实现
排序就是把stuArr数组内所有学生的信息,根据某一科目成绩或总分的高低,来把学生“挂”在平衡二叉树的结点上。因此排序函数showAs需要传入一个int型的参数check,来记录需要用哪一科目的成绩来进行排序,check等于1、2、3、4,依次对用通过语文、数学、英语、总分排序。
排序后,只需通过中序遍历,遍历平衡二叉树并调用每一个结点上的student类型的数据的show_inf()函数,就可以显示结点上存储的学生的信息了。
代码实现如下图4.5
图4.5排序和输出功能的实现
4.4通过成绩查找以及删除功能的实现
查找和删除都是在平衡二叉树上实现的,因此,通过成绩查找和通过成绩查找并删除功能的实现,只需分别调用平衡二叉树的search功能和delete功能即可实现。
在排序时会用一个int类型数据sort记录排序方式,也就是通过sort对应的科目进行排序。在查找时,只能通过sort对应的科目进行查找,因为树是以sort对应的科目的成绩生成的。
代码实现如图4.6
图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;
};