二叉排序树:

定义

        二叉排序树,又叫二叉查找树,它或者是一棵空树;或者是具有以下性质的二叉树:
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. maven仓库的配置

    今天接触到了maven的配置问题,特此来记录一下,也给我这样的小白一个参考. 目标:eclipse中配置指定的maven仓库. 说明: 使用过maven项目的话,会在你的C:\Users\lenovo ...

  2. netcore mvc 的简单实现

    实现的功能 简单的路由系统 支持中间件 简单Filter支持 只支持HttpPost.HttpGet 使用Dotliquid做为视图渲染引擎 核心实现 HttpChannel 复制监听Tcp请求,并按 ...

  3. SpringBoot中获取微信用户信息从未如此简单!

    前言 不知道你是否参加过拼多多上邀请微信好友砍价功能,这个功能实现首先需要考虑的就是获取微信用户的信息.获取用户信息就是获取公众号下微信用户的信息,今天我就来讲讲如何从公众号下获取微信用户信息. 需要 ...

  4. AirFlow常见问题汇总

    airflow常见问题的排查记录如下: 1,airflow怎么批量unpause大量的dag任务 ​ 普通少量任务可以通过命令airflow unpause dag_id命令来启动,或者在web界面点 ...

  5. SqlServer Left、Right、CharIndex函数

    LEFT 函数:返回字符串中从左边开始指定个数字符 RIGT.H 函数:返回字符串从右边开始指定个数字符 len函数:LEN 函数返回文本字段中值的长度. CHARINDEX函数:CHARINDEX ...

  6. C++基础之迭代器

    迭代器的分类 插入迭代器(insert iterator):绑定一个容器上后可以向容器中插入元素: 流迭代器(stream iterator):绑定在输入输出流中,可以遍历关联的流: 反向迭代器(re ...

  7. Java匹马行天下之C国程序员的秃头原因

    Java帝国的崛起 前言: 分享技术之前先请允许我分享一下黄永玉老先生说过的话:“明确的爱,直接的厌恶,真诚的喜欢.站在太阳下的坦荡,大声无愧地称赞自己.” <编程常识知多少> <走 ...

  8. C++ 变量判定的螺旋法则

    C++ 中一个标识符配合着各种修饰界定符,使得标识符的本意不那么直观一眼就能看出,甚至需要仔细分析,才能知道该标识符的具体你含义. 比如: void (*signal(int, void (*fp)( ...

  9. Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)

    在上一篇文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码 ,中我们看到 代码:XMLConfigBuilder parser = new XMLConfigBuilder(read ...

  10. MySQL中常用到的关于时间的SQL

    -- 今天 SELECT DATE_FORMAT(NOW(),'%Y-%m-%d 00:00:00') AS dayStart;SELECT DATE_FORMAT(NOW(),'%Y-%m-%d 2 ...