二叉排序树:

定义

        二叉排序树,又叫二叉查找树,它或者是一棵空树;或者是具有以下性质的二叉树:
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. vue.js安装教程

    vue.js环境搭建 1.下载node.js 网址:https://nodejs.org/en/ 版本:v10.16.3 2.安装node.js Node.js下载如下所示: 检查nodejs是否安装 ...

  2. 选择正确的 Fragment#commitXXX() 函数

    转自: http://www.tuicool.com/articles/q6R7nii 最新版本(v24.0.0)的 Support v4 库中的 FragmentTransaction 添加了 co ...

  3. VMware 虚拟机三种网络模式详解

    一.前言 Vmware 为我们提供了三种网络工作模式,分别是:Bridged(桥接模式).NAT(网络地址转换模式).Host-only(仅主机模式). 二.VMware 的几个常见虚拟设备 打开 V ...

  4. 你好,C语言

    对于我来说,C语言就和陌生人一样,对他完全不了解,更不要提什么C++了,这就要我主动和他打招呼,深入认识了解它了哈.目前对于C语言的理解,只知道他的的功强大,能操作硬件,编写各类驱动,强悍的LINUX ...

  5. [VB.NET Tips]再谈字符串连接之内置池

    CLR自动维护一个称为"内置池"(暂存池)(intern pool)的表,在编译时此表包含程序中声明的每个唯一的字符串常量的单个实例,以及以编程方式创建的String类的任何唯一实 ...

  6. [Leetcode] 第289题 生命游戏

    一.题目描述 根据百度百科,生命游戏,简称为生命,是英国数学家约翰·何顿·康威在1970年发明的细胞自动机. 给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞.每个细胞具有一个初 ...

  7. 【linux】【Go】Centos7安装go1.13环境

    前言     Go(又称Golang)是Google开发的一种静态强类型.编译型.并发型,并具有垃圾回收功能的编程语言. 罗伯特·格瑞史莫(Robert Griesemer),罗勃·派克(Rob Pi ...

  8. Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例

    在Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们看到了XMLConfigBuilder(xml配置解析器)的实例化.而且这个实例化过程在文章:Mybatis源码解析,一步一步从浅 ...

  9. 如何在服务器中安装mysql 以及安装禅道

    安装mysql:以下命令在xshell 中输入: 1.卸载mysql :yum -y remove mysql* 2.查找mysql命名安装的所有文件: find / -name mysql  只能删 ...

  10. 品Spring:SpringBoot和Spring到底有没有本质的不同?

    现在的Spring相关开发都是基于SpringBoot的. 最后在打包时可以把所有依赖的jar包都打进去,构成一个独立的可执行的jar包.如下图13: 使用java -jar命令就可以运行这个独立的j ...