二叉排序树:

定义

        二叉排序树,又叫二叉查找树,它或者是一棵空树;或者是具有以下性质的二叉树:
1. 若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
2. 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
3. 它的左右子树也分别为二叉排序树。
比如下图就是一棵普通的二叉排序树:

   如果按照中序遍历的顺序,一棵二叉排序树的输出结果就刚好是按照从小到大的顺序输出的,可以运用于二分算法。

先对其数据结构进行定义:

typedef struct Binary_Tree_node
{
int data; //数据域
struct Binary_Tree_node* lchild, * rchild; //左右孩子结点 }Binode, * BiTree;

然后是插入操作:

//假设没有相等的data,这里我们不考虑相等的数据
//插入结点
void Insert_Binary_Tree(BiTree& bst ,int t) //bst是根节点
{ if (bst == NULL) //空树或者递归到了叶结点
{
BiTree newp = new Binode;
newp->data = t;
newp->lchild = NULL;
newp->rchild = NULL;
bst = newp;
}
else
{
if (t > bst->data) //比本结点大,则插入其右孩子结点,小就插入左孩子结点
Insert_Binary_Tree(bst->rchild, t);
else
Insert_Binary_Tree(bst->lchild, t);
}
}

创建一棵树:

//创建一棵二叉排序树
BiTree Create_Binary_Tree()
{
BiTree bst = NULL;
int t;
cin >> t;
while (t != ENDKEY) //只要输入不等于-1,就一直往树里插入元素
{
Insert_Binary_Tree(bst, t);
cin >> t;
}
return bst;
}

删除操作:删除操作比较复杂,本篇博客主要是记录AVL,所以此处不做赘述

BiTree Delete_Binary_Tree(BiTree bst, int t)
{
Binode* newp, * f, * s, * q;
newp = bst;
f = NULL;
while (newp)
{
if (newp->data == t)
break;
f = newp;
if (t > newp->data)
newp = newp->rchild;
else
newp = newp->lchild;
}
if (newp == NULL)
return bst;
if (newp->lchild == NULL)
{
if (f == NULL)
bst = bst->rchild;
else if (f->lchild == newp)
f->lchild = newp->rchild;
else
f->rchild = newp->rchild; delete[]newp;
}
else if (newp->rchild == NULL)
{
if (f == NULL)
bst = bst->lchild;
else if (f->lchild == newp)
f->lchild = newp->lchild;
else
f->rchild = newp->lchild; delete[]newp;
}
else
{
q = newp;
s = newp->lchild;
while (s->rchild)
{
q = s;
s = s->rchild;
}
if (q == newp)
q->lchild = s->lchild;
else
q->rchild = s->lchild;
newp->data = s->data;
delete[]s;
}
return bst;
}

搜索二叉树:

//搜索二叉树,根据输入的数据搜索二叉树,返回这个数据的结点
BiTree Search_Binary_Tree(BiTree bst, int t)
{
if (bst == NULL)
return NULL;
if (bst->data == t)
return bst;
else if (t > bst->data)
return Search_Binary_Tree(bst->rchild, t);
else
return Search_Binary_Tree(bst->lchild, t);
}

平衡二叉排序树:

可是当一棵二叉排序树的某个节点的一枝相比于另一枝太长,搜索的时间复杂度就会变成O(n),而为了提高效率,提出了AVL树,即平衡二叉排序树。 例如下图就是一棵不平衡的二叉树:



像这样如果要搜索0元素就会变得和线性表的时间复杂度一样。 AVL树是一种比查找二叉树还特别的树,这种树就可以帮助我们解决二叉查找树刚才的那种所有节点都倾向一边的缺点的。具有如下特性:

1、具有二叉查找树的全部特性。

2、每个节点的左子树和右子树的高度差至多等于1。

例如:图一就是一颗AVL树了,而图二则不是(节点右边标的是这个节点的高度)。





按照这种规则进行建立二叉树,可以保证这棵二叉树始终是左右相对平衡的,可以保证搜索的时间复杂度达到O(log n),提高了效率。

针对树的失衡有四种情况,我们总结如下:

1、左左型:做右旋

2、右右型:做左旋

3、左右型:先左旋,再右旋

4、右左型:先右旋,再左旋

下面一个一个写,对于实现每个旋转的函数可以作为私有的对内接口(C++),不对外开放使用

1、左左型:做右旋:

像下面这种:



我们做如下右旋操作:



即顺时针旋转结点,使双亲结点被自己的左孩子替代,然后自己变成左孩子结点的右孩子结点

代码实现:

		  //左左型单旋转
void Lleft(BiTree &bst) //bst为不平衡的结点
{
BiTree lch = bst->lchild; //保存不平衡结点的左孩子结点
bst->lchild = lch->rchild;
lch->rchild = bst;
bst = lch;
}

2、右右型:做左旋:

像下面这种:



逆时针旋转结点,使自己被自己的右孩子替代,然后自己变成右孩子的左孩子结点

代码实现:

//右右型单旋转
void Rright(BiTree &bst) //bst为不平衡的结点
{
BiTree rch = bst->rchild; //保存不平衡结点的右孩子结点
bst->rchild = rch->lchild;
rch->lchild = bst;
bst = rch;
}

3、左右型:先左旋,再右旋:

像下面这种:



先对其左旋将其变成左左型,再右旋让其平衡

代码实现:

//左右型双旋转
void Lright(BiTree &bst)
{
//先做左旋,将其变成左左型
BiTree lch = bst->lchild;
BiTree lrch = bst->lchild->rchild;
bst->lchild = lrch;
bst->lchild->lchild = lch;
bst->lchild->lchild->rchild = NULL; //可能存在bug todo,目前来看没有哈哈哈 Lleft(bst); //再将其右旋
}

4、右左型:先右旋,再左旋:

老规矩,先贴图:



代码实现:

//右左型双旋转
void Rleft(BiTree &bst)
{
//先做右旋,将其变成右右型
BiTree rch = bst->rchild;
BiTree rlch = bst->rchild->lchild;
bst->rchild = rlch;
bst->rchild->rchild = rch;
bst->rchild->rchild->lchild = NULL; Rright(bst); //再右旋
}

实现重点来了

我们每插入一个结点元素都要保证整棵树的每一个结点是平衡的,就要在插入这个结点后,从这个插入的结点往上逐个双亲结点去检查,看是否破坏了树的平衡,如果破坏了则对其进行左右旋转操作,保证其平衡性,直到树的根节点。

因为树的递归性,在插入的时候是递归插入,

按照栈的顺序,递归的过程中第一次递归的函数先入栈,以此类推,然后直到到达结束条件,前面的才依次出栈,

所以可以在插入函数最后面使用检查每个结点的函数,出栈过程中,就可以沿着新插入结点一路回退检查每个双亲结点了。

注意最后一行

代码实现:

//插入结点
void Insert_Binary_Tree(BiTree& bst ,int t) //bst是根节点
{ if (bst == NULL) //空树或者叶结点
{
BiTree newp = new Binode;
newp->data = t;
newp->lchild = NULL;
newp->rchild = NULL;
bst = newp;
}
else
{
if (t > bst->data)
Insert_Binary_Tree(bst->rchild, t);
else
Insert_Binary_Tree(bst->lchild, t);
}
//在最后检查这个结点,在递归的时候就可以将递归过程中从根节点往下的所有结点都检查了,按照出栈顺序,可以保证都是在插入后才依次向上检查的。
Check_Binary_Tree(bst);
}

要实现这一功能就必须有一个检查是否平衡的依据———就是每个结点的左右子树的深度。这个函数是作为私有的对内接口。

计算深度的实现:

//一个结点的深度
int Binary_Tree_height(BiTree bst) //bst为要计算的结点
{
if (bst == NULL)
return 0;
int l = Binary_Tree_height(bst->lchild);
int r = Binary_Tree_height(bst->rchild);
return max(l, r) + 1;
}

然后是一个检查这个结点是否平衡的函数,对外:

检查平衡函数:

//如果出现不平衡的情况就旋转
void Check_Binary_Tree(BiTree &bst) //bst为要检查的结点
{
if (bst == NULL)
return;
if (Binary_Tree_height(bst->lchild) - Binary_Tree_height(bst->rchild) > 1)
{
if (Binary_Tree_height(bst->lchild->lchild) > Binary_Tree_height(bst->lchild->rchild))
Lleft(bst);
else
Lright(bst);
}
if (Binary_Tree_height(bst->rchild) - Binary_Tree_height(bst->lchild) > 1)
{
if (Binary_Tree_height(bst->rchild->rchild) > Binary_Tree_height(bst->rchild->lchild))
Rright(bst);
else
Rleft(bst);
}
}

最后,对代码进行一下汇总:

AVL.h文件
#pragma once
#include<iostream>
constexpr auto ENDKEY = -1;
template<typename T1, typename T2>
constexpr auto max(T1 x, T2 y) { return x>y?x:y; } using namespace std; typedef struct Binary_Tree_node
{
int data; //数据域
struct Binary_Tree_node* lchild, * rchild; //左右孩子结点
}Binode, * BiTree; //访问二叉树
void visit(int c, int level)
{
printf("%d位于第%d层\n", c, level);
} //中序遍历
void Midorder_Traverse(BiTree T, int level)
{
if (T)
{
Midorder_Traverse(T->lchild, level + 1);
visit(T->data, level);
Midorder_Traverse(T->rchild, level + 1);
}
} //一个结点的深度
int Binary_Tree_height(BiTree bst)
{
if (bst == NULL)
return 0;
int l = Binary_Tree_height(bst->lchild);
int r = Binary_Tree_height(bst->rchild);
return max(l, r) + 1;
} //遍历整棵树,如果出现不平衡的情况就旋转 //左左型单旋转
void Lleft(BiTree &bst)
{
BiTree lch = bst->lchild; //保存不平衡结点的左孩子结点
bst->lchild = lch->rchild;
lch->rchild = bst;
bst = lch;
} //右右型单旋转
void Rright(BiTree &bst)
{
BiTree rch = bst->rchild; //保存不平衡结点的右孩子结点
bst->rchild = rch->lchild;
rch->lchild = bst;
bst = rch;
} //左右型双旋转
void Lright(BiTree &bst)
{
//先做左旋,将其变成左左型
BiTree lch = bst->lchild;
BiTree lrch = bst->lchild->rchild;
bst->lchild = lrch;
bst->lchild->lchild = lch;
bst->lchild->lchild->rchild = NULL; //可能存在bug todo Lleft(bst);
} //右左型双旋转
void Rleft(BiTree &bst)
{
//先做右旋,将其变成右右型
BiTree rch = bst->rchild;
BiTree rlch = bst->rchild->lchild;
bst->rchild = rlch;
bst->rchild->rchild = rch;
bst->rchild->rchild->lchild = NULL; Rright(bst);
} void Check_Binary_Tree(BiTree &bst)
{
if (bst == NULL)
return;
if (Binary_Tree_height(bst->lchild) - Binary_Tree_height(bst->rchild) > 1)
{
if (Binary_Tree_height(bst->lchild->lchild) > Binary_Tree_height(bst->lchild->rchild))
Lleft(bst);
else
Lright(bst);
}
if (Binary_Tree_height(bst->rchild) - Binary_Tree_height(bst->lchild) > 1)
{
if (Binary_Tree_height(bst->rchild->rchild) > Binary_Tree_height(bst->rchild->lchild))
Rright(bst);
else
Rleft(bst);
}
//Check_Binary_Tree(bst->lchild);
//Check_Binary_Tree(bst->rchild);
} //假设没有相等的data
//插入结点
void Insert_Binary_Tree(BiTree& bst ,int t) //bst是根节点
{ if (bst == NULL) //空树或者叶结点
{
BiTree newp = new Binode;
newp->data = t;
newp->lchild = NULL;
newp->rchild = NULL;
bst = newp;
}
else
{
if (t > bst->data)
Insert_Binary_Tree(bst->rchild, t);
else
Insert_Binary_Tree(bst->lchild, t);
}
Check_Binary_Tree(bst);
} //创建一棵二叉排序树
BiTree Create_Binary_Tree()
{
BiTree bst = NULL;
int t;
cin >> t;
while (t != ENDKEY)
{
Insert_Binary_Tree(bst, t);
cin >> t;
}
return bst;
} //二叉排序树的删除
BiTree Delete_Binary_Tree(BiTree bst, int t)
{
Binode* newp, * f, * s, * q;
newp = bst;
f = NULL;
while (newp)
{
if (newp->data == t)
break;
f = newp;
if (t > newp->data)
newp = newp->rchild;
else
newp = newp->lchild;
}
if (newp == NULL)
return bst;
if (newp->lchild == NULL)
{
if (f == NULL)
bst = bst->rchild;
else if (f->lchild == newp)
f->lchild = newp->rchild;
else
f->rchild = newp->rchild; delete[]newp;
}
else if (newp->rchild == NULL)
{
if (f == NULL)
bst = bst->lchild;
else if (f->lchild == newp)
f->lchild = newp->lchild;
else
f->rchild = newp->lchild; delete[]newp;
}
else
{
q = newp;
s = newp->lchild;
while (s->rchild)
{
q = s;
s = s->rchild;
}
if (q == newp)
q->lchild = s->lchild;
else
q->rchild = s->lchild;
newp->data = s->data;
delete[]s;
}
Check_Binary_Tree(bst);
return bst;
} //搜索二叉树
BiTree Search_Binary_Tree(BiTree bst, int t)
{
if (bst == NULL)
return NULL;
if (bst->data == t)
return bst;
else if (t > bst->data)
return Search_Binary_Tree(bst->rchild, t);
else
return Search_Binary_Tree(bst->lchild, t);
}
测试数据
#include<iostream>
#include"BST.h"
using namespace std;
int main()
{
BiTree bst = Create_Binary_Tree();
int level = 1;
Midorder_Traverse(bst, level);
system("pause");
}

测试结果:

AVL平衡二叉查找树的更多相关文章

  1. 平衡树初阶——AVL平衡二叉查找树+三大平衡树(Treap + Splay + SBT)模板【超详解】

    平衡树初阶——AVL平衡二叉查找树 一.什么是二叉树 1. 什么是树. 计算机科学里面的树本质是一个树状图.树首先是一个有向无环图,由根节点指向子结点.但是不严格的说,我们也研究无向树.所谓无向树就是 ...

  2. AVL树(平衡二叉查找树)

    首先要说AVL树,我们就必须先说二叉查找树,先介绍二叉查找树的一些特性,然后我们再来说平衡树的一些特性,结合这些特性,然后来介绍AVL树. 一.二叉查找树 1.二叉树查找树的相关特征定义 二叉树查找树 ...

  3. 【查找结构3】平衡二叉查找树 [AVL]

    在上一个专题中,我们在谈论二叉查找树的效率的时候.不同结构的二叉查找树,查找效率有很大的不同(单支树结构的查找效率退化成了顺序查找).如何解决这个问题呢?关键在于如何最大限度的减小树的深度.正是基于这 ...

  4. 数据结构-自平衡二叉查找树(AVL)详解

    介绍: 在计算机科学中,AVL树是最先发明的自平衡二叉查找树. 在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树. 查找.插入和删除在平均和最坏情况下都是O(log n).增 ...

  5. 算法学习 - 平衡二叉查找树实现(AVL树)

    平衡二叉查找树 平衡二叉查找树是非常早出现的平衡树,由于全部子树的高度差不超过1,所以操作平均为O(logN). 平衡二叉查找树和BS树非常像,插入和删除操作也基本一样.可是每一个节点多了一个高度的信 ...

  6. 平衡二叉查找树 AVL 的实现

    不同结构的二叉查找树,查找效率有很大的不同(单支树结构的查找效率退化成了顺序查找).如何解决这个问题呢?关键在于如何最大限度的减小树的深度.正是基于这个想法,平衡二叉树出现了. 平衡二叉树的定义 (A ...

  7. 006-数据结构-树形结构-二叉树、二叉查找树、平衡二叉查找树-AVL树

    一.概述 树其实就是不包含回路的连通无向图.树其实是范畴更广的图的特例. 树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合. 1.1.树的特性: 每个结点有零个或多个子 ...

  8. 二叉搜索树、AVL平衡二叉搜索树、红黑树、多路查找树

    1.二叉搜索树 1.1定义 是一棵二叉树,每个节点一定大于等于其左子树中每一个节点,小于等于其右子树每一个节点 1.2插入节点 从根节点开始向下找到合适的位置插入成为叶子结点即可:在向下遍历时,如果要 ...

  9. 二叉查找树(二叉排序树)的详细实现,以及随机平衡二叉查找树Treap的分析与应用

    这是一篇两年前写的东西,自我感觉还是相当不错的Treap教程.正好期末信息科学技术概论课要求交一个论文,就把这个东西修改了一下交了,顺便也发到这里吧. 随机平衡二叉查找树Treap的分析与应用 1.序 ...

随机推荐

  1. 【Hystrix】实现服务隔离和降级

    一.背景 1.1 服务熔断 1.2 服务降级 1.3 服务隔离 1.4 总结 二.使用Hystrix实现服务隔离和降级 2.1 Hytrix 简介 2.2 线程池方式 2.3 信号量 三.项目搭建 3 ...

  2. SIA-GateWay之API网关安装部署指南

    SIA-GATEWAY是基于SpringCloud微服务生态体系下开发的一个分布式微服务网关系统.具备简单易用.可视化.高可扩展.高可用性等特征,提供云原生.完整及成熟的接入服务解决方案.本文介绍AP ...

  3. 锁和synchronized

    锁的常见概念 互斥: 同一时刻只有一个线程执行 临界区:一段需要互斥执行的代码 细粒度锁: 用不同的锁对受保护资源进行精细化管理. 细粒度锁可以提高并行度,是性能优化的一个重要手段 死锁 :一组互相竞 ...

  4. Spring Cloud(二):服务消费者

    创建“服务消费者” 创建一个基础的Spring Boot工程,命名为springboot-consumer,并在pom.xml中引入需要的依赖内容: <dependency> <gr ...

  5. 松软带你学开发-SQLSELECTDISTINCT语句

    SQL SELECT DISTINCT 语句 在表中,可能会包含重复值.这并不成问题,不过,有时您也许希望仅仅列出不同(distinct)的值. 关键词 DISTINCT 用于返回唯一不同的值. 语法 ...

  6. python 虚拟环境下导入模块出现no matching modules 的解决办法

    问题原因:pip版本过低 解决办法:升级pip                             命令行为   python -m pip install -U pip

  7. sersync 实时同步

    1.什么是实时同步 ​ 监控一个目录的变化, 当该目录触发事件(创建\删除\修改) 就执行动作, 这个动作可以是 rsync同步 ,也可以是其他. 2.为什么要实时同步 1.能解决nfs单点故障问题. ...

  8. 使用dbutils

    环境准备: 包结构: mysql导出sql脚本: //product CREATE TABLE `product` ( `id` int(10) NOT NULL AUTO_INCREMENT, `n ...

  9. 【赶快收藏】Hystrix实战,优雅提升系统的鲁棒性

    背景 最近接手了一个系统,其功能都是查询.查询分了两种方式,一种是公司集团提供的查询能力,支持全国各个省份的查询,但是业务高峰期时服务响应比较慢:另外一种是各省的分公司都分别提供了对应的查询能力,但是 ...

  10. 基于SpringBoot实现AOP+jdk/CGlib动态代理详解

    动态代理是一种设计模式.在Spring中,有俩种方式可以实现动态代理--JDK动态代理和CGLIB动态代理. JDK动态代理 首先定义一个人的接口: public interface Person { ...