二叉树与AVL树
二叉树
什么是二叉树?
父节点至多只有两个子树的树形结构成为二叉树。如下图所示,图1不是二叉树,图2是一棵二叉树。
图1 普通的树 图2 二叉树
如果一棵树所有的非叶子节点都有两个子节点,则称该树为完全二叉树,图2就是一棵完全二叉树。
二叉查找树(ADT)
二叉树一个重要的应用是二差查找树,顾名思义,二叉查找树是二叉树在查找方面的应用。根据以往的知识,如果给你一个数组或者是链表,可能需要遍历一整个数组或者是量表才能找到需要查找的目标。使用二叉树作为查找的数据结构,能够大大缩小查找的深度(若链表和数组的长度为N,一棵二叉树的深度为logN),提高查找的效率。
如何定义一棵查找树?
设每一个节点对应一个键值,而该节点的左子节点对用的数值小于该节点的值,该节点的右子节点的数值大于该节点的值。如下图所示:
图3 二差查找树 图4 非二差查找树
图3是二叉查找树,因为他总数满足上述的条件,而图4不是二叉查找树,图中红色框住的部分不满足二叉查找树的条件(父节点6<左子节点7)。
代码:
struct Tree{
double value;
Tree *left;
Tree *right;
};
TreeNode表示二叉树节点。其中value表示树对应的值,left表示该节点的左节点指针,right表示该节点的右节点指针。
二叉查找树的重要操作
1.查找
1.1 查找固定的数值
给定一个数值,查找二叉树中是否有对应的数值,如果树中包含该数值,则返回数值对应的节点,否则,返回NULL。查找的过程:
首先定位到跟节点,如果查找的数值跟节点的数值相等,则根节点为所求;否则,如果数值小于根节点的数值,则查找根节点的左节点,相反,则查找跟节点的右节点。这样一直遍历下去,知道找到对应的或者或者已经不能继续往下查找为止(即已经到达子节点)。
代码:
Tree* Find(double value,Tree *t){
if(t==NULL)
return NULL;
if(value==t->value)
return t;
else if(value<t->value)
return Find(value,t->left);
else
return Find(value,t->right);
}
1.2查找最大值或最小值
二叉查找树一个很大的特点是左子节点的数值<父节点的数值,而右子节点的数值>父节点的数值,因此查找最大值或者最小值就相当的方便,只需要从跟节点开始,不断遍历左子结点,直到到达叶子节点,就可以得到最小值;而查找最大值则从根节点开始遍历右子结点,直到到达叶子节点,对用的数值即为最大值。
代码:
超找最小元素的节点
Tree* FindMin(Tree* t){
if(t==NULL)
return NULL;
if(t->left==NULL)
return t;
else{
return FindMin(t->left);
}
}
查找最大元素的节点
Tree* FindMax(Tree *t){
if(t==NULL)
return NULL;
if(t->right==NULL)
return t->right;
else
return FindMax(t->right);
}
2. 插入
插入操作是将一个数值插入到二叉查找树中的过程,插入后的树依然满足二叉查找树的条件。一棵二叉查找树的构建过程,就是一个不断将元素插入到二叉树的过程。
插入的操作最关键的是一个查找的过程,如果在二叉树中找到要插入的数值,则什么都不用做,如果找不到,只需要在最后我们查找的叶子节点上新增加一个子节点即可。举一个例子:
上图是一棵二叉树,如果我们要插入11,则按以下步骤进行:
再比如要找17,按以下步骤查找:
代码:
void Insert(double value,Tree* t){
if(t==NULL){
t = new Tree();
if(t==NULL)
return;
else{
t->value=value;
t->left=NULL;
t->right=NULL;
}
}
if(t->value>value)
Insert(value,t->left);
else
Insert(value,t->right);
}
3. 删除
删除操作就是删除键值与数值相同的节点。删除操作相比查找和插入来说是一个较为复杂的过程。如果要删除的节点就是叶子节点,直接将该节点删除即可,但是,如果要删除非叶子节点,就需要通过调整其他节点的位置来构建新的二叉树。一般来说,直接用该节点的右子树的最小值替换该节点的值,然后再删除右子树的最小节点即可。
例子:
AVL树
虽说二叉查找树是一种优秀的数据结构,能够大大降低数据查询的复杂度。但是,并不是说有情况下二叉树都能够达到快速查找的目的。
我们发现,如果按照[7,10,11,12,14,15,18]这样的顺序一个个元素进行插入的话就会出现右图所示的二叉树,这样的二叉树跟一个链表几乎是没有区别的,查找的效率一样,没有体现出二叉树的优势。出现这种原因是构建二叉树的过程中没有平衡节点的左右子树的高度。根节点7的右子树有很高的深度,但是左子树是空的。我们需要的是一棵左右节点平衡的二叉树,而其中一种传统的平衡二叉树是AVL树。
定义:AVL树是二叉树,其各个节点的左右子树的高度相差不超过1。
定义:数的高度可以看做是节点与最低的叶子节点的距离。跟节点的高度最大,而叶子节点的高度为0,一个不存在的节点的高度定义为-1。
例如:左图中节点12的高度为2,节点18的高度为0,而右图中节点12的高度为3,节点18的高度为1。
左图是一棵AVL树,右图不是一棵AVL树,因为右图节点15的左右子树的高度相差2(左子树的高度为-1,右子树的盖度为1)。
AVL树的构建
AVL树的构建同样是不断将元素插入的过程,但是与二叉查找树不同,AVL树在插入的过程中需要满足AVL树的条件,如果发现插入新的元素后不能满足AVL条件,需要通过调整元素的位置直到满足条件。
元素的插入无非就只有以下四种情况:1.左子树插入一个左节点;2.左子树插入一个右节点;3.右子树插入一个左节点;4.右子树插入一个右节点。
其中,1和4、2和3是对称的操作,因此下面只讨论1和2两种情况。
1. 左子树插入一个左节点
如上图所述,左边是原始的二叉树,该二叉树满足AVL树的条件,在插入元素5后变成了右边的二叉树,而此时不满足AVL树的条件,因为节点10的左右两棵子树的高度相差2(左子树的高度为1,右子树的高度为-1)。
此时需要通过对子树进行调整才能让二叉树再次满足AVL的条件。
如上图所示,调整后的二叉树重新变成一棵AVL树,则种调整的方法称为“左旋转”,通过旋转调整节点的位置,使二叉树满足AVL树的条件。同理,如果新增的元素为右子树的右子树,而且新增后子树的左右子树高度相差为2,此时进行“右旋转”即可调整为AVL树。
2. 左子树插入一个右节点
如上图所述,左边是原始的二叉树,该二叉树满足AVL树的条件,在插入元素8后变成了右边的二叉树,而此时不满足AVL树的条件,因为节点10的左右两棵子树的高度相差2(左子树的高度为1,右子树的高度为-1)。
与情况1(左子树新增左节点)不一样,此时不能通过一个简单的“坐旋转”来调整左右子树的高度。怎么办呢?需要通过两次“旋转”,显示通过对元素7进行右旋转,然后再对10进行左旋转。如下图所示:
同理,对于右子树插入一个左节点的情况,如果此时不符合AVL树的条件,需要先进性“左旋转”,再进行“右旋转”即可。
代码:
AVLTree* AVLInsert(double value,AVLTree * tree){
if(tree==NULL){
tree = new AVLTree();
tree->value=value;
tree->left=NULL;
tree->right=NULL;
tree->height=;
return;
}
if(value>tree->value){
tree->right=AVLInsert(value,tree->right);
if(Height(tree->right)-Height(tree->left)==){
if(value>tree->right->value)
tree=SingleRotateWithRight(tree);
else
tree=DoubleRouteWithRight(tree);
}
}
else{
tree->left=AVLInsert(value,tree->left);
if(Height(tree->left)-Height(tree->right)==){
if(value<tree->left->value)
tree=SingleRotateWithLeft(tree);
else
tree=DoubleRouteWithLeft(tree);
}
}
tree->height = Height(tree->left)>Height(tree->right)?Height(tree->left)+:Height(tree->right)+;
return tree;
}
//左旋转
AVLTree* SingleRotateWithLeft(AVLTree * tree){
AVLTree * left = tree->left;
tree->left=left->right;
left->right=tree;
tree->height=Height(tree->left)>Height(tree->right)?Height(tree->left)+:Height(tree->right)+;
left->height=Height(left->left)>Height(left->right)?Height(left->left)+:Height(left->right)+;
return left;
}
//右旋转
AVLTree* SingleRotateWithRight(AVLTree * tree){
AVLTree * right = tree->right;
tree->right=right->left;
right->left=tree;
tree->height=Height(tree->left)>Height(tree->right)?Height(tree->left)+:Height(tree->right)+;
right->height=Height(right->left)>Height(right->right)?Height(right->left)+:Height(right->right)+;
return right;
}
//右—左双旋转
AVLTree* DoubleRouteWithLeft(AVLTree* tree){
tree->left = SingleRotateWithRight(tree->left);
return SingleRotateWithLeft(tree);
}
//右—左双旋转
AVLTree* DoubleRouteWithRight(AVLTree* tree){
tree->right = SingleRotateWithLeft(tree->right);
return SingleRotateWithRight(tree);
}
二叉树与AVL树的更多相关文章
- 二叉树,AVL树和红黑树
为了接下来能更好的学习TreeMap和TreeSet,讲解一下二叉树,AVL树和红黑树. 1. 二叉查找树 2. AVL树 2.1. 树旋转 2.1.1. 左旋和右旋 2.1.2. 左左,右右,左右, ...
- 二叉树-二叉查找树-AVL树-遍历
一.二叉树 定义:每个节点都不能有多于两个的儿子的树. 二叉树节点声明: struct treeNode { elementType element; treeNode * left; treeNod ...
- python常用算法(5)——树,二叉树与AVL树
1,树 树是一种非常重要的非线性数据结构,直观的看,它是数据元素(在树中称为节点)按分支关系组织起来的结构,很像自然界中树那样.树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可用树形 ...
- 5分钟了解二叉树之AVL树
转载请注明出处:https://www.cnblogs.com/morningli/p/16033733.html AVL树是带有平衡条件的二叉查找树,其每个节点的左子树和右子树的高度最多相差1.为了 ...
- 二叉树之AVL树的平衡实现(递归与非递归)
这篇文章用来复习AVL的平衡操作,分别会介绍其旋转操作的递归与非递归实现,但是最终带有插入示例的版本会以递归呈现. 下面这张图绘制了需要旋转操作的8种情况.(我要给做这张图的兄弟一个赞)后面会给出这八 ...
- 二叉树之AVL树
高度为 h 的 AVL 树,节点数 N 最多2^h − 1: 最少N(h)=N(h− 1) +N(h− 2) + 1. 最少节点数n 如以斐波那契数列可以用数学归纳法证明: 即: N(0) = 0 ( ...
- 04-树4. Root of AVL Tree-平衡查找树AVL树的实现
对于一棵普通的二叉查找树而言,在进行多次的插入或删除后,容易让树失去平衡,导致树的深度不是O(logN),而接近O(N),这样将大大减少对树的查找效率.一种解决办法就是要有一个称为平衡的附加的结构条件 ...
- AVL树和伸展树 -数据结构(C语言实现)
读数据结构与算法分析 AVL树 带有平衡条件的二叉树,通常要求每颗树的左右子树深度差<=1 可以将破坏平衡的插入操作分为四种,最后通过旋转恢复平衡 破坏平衡的插入方式 描述 恢复平衡旋转方式 L ...
- 【数据结构】什么是AVL树
目录 什么是AVL树 1. 什么是AVL树 2. 节点的实现 3. AVL树的调整 3.1 LL旋转 3.2 RR旋转 3.3 RL旋转 3.4 LR旋转 什么是AVL树 二叉查找树的一个局限性就是有 ...
随机推荐
- BZOJ 4407: 于神之怒加强版 [莫比乌斯反演 线性筛]
题意:提前给出\(k\),求\(\sum\limits_{i=1}^n \sum\limits_{j=1}^m gcd(i,j)^k\) 套路推♂倒 \[ \sum_{D=1}^n \sum_{d|D ...
- centos 7 配置 到多站点设置
背景 : 前面配置了 win2008 WAMP环境, 这次因为一些事情 新买了服务器 只有 win2003 和 win2012, 试着配置2012 WAMP环境 发现比 2008 缺的配置文件多很多 ...
- MySQL Community Server 5.7安装详细步骤
mysql社区版安装配置步骤较繁琐,几经搜索之后才成功安装,此文将所有的安装步骤及安装过程中遇到的问题进行了总结 1. 下载MySQL社区版 最新版下载地址:https://dev.mysql ...
- Java实现单链表的快速排序和归并排序
本文描述了LeetCode 148题 sort-list 的解法. 题目描述如下: Sort a linked list in O(n log n) time using constant space ...
- 新版Azure Automation Account 浅析(三) --- 用Runbook管理AAD Application Key
新版Azure Automation Account 浅析(三) --- 用Runbook管理AAD应用的Key 前篇讲过有一个面向公众的Runbook库,社区和微软一直往其中加入新的Runbook, ...
- linux使用tcpdump抓包工具抓取网络数据包,多示例演示
tcpdump是linux命令行下常用的的一个抓包工具,记录一下平时常用的方式,测试机器系统是ubuntu 12.04. tcpdump的命令格式 tcpdump的参数众多,通过man tcpdump ...
- iOS 关于文件的操作
最近做东西,遇到了使用文件方面的问题,花了点时间把文件研究了一下! 一 关于文件路径的生成 我用的方法是: -(NSString*)dataFilePath { NSArray * paths = ...
- [bzoj3998][TJOI2015]弦论-后缀自动机
Brief Description 给定一个字符串, 您需要求出他的严格k小子串或非严格k小子串. Algorithm Design 考察使用后缀自动机. 首先原串建SAM, 然后如果考察每个状态代表 ...
- elasticsearch2.3.3集群搭建踩到的坑
本文来自我的github pages博客http://galengao.github.io/ 即www.gaohuirong.cn 摘要: 作者原来搭建的环境是0.95版本 现在升级到2.3.3版本, ...
- js分页功能实现
实现一个js的分页并在弹出框中显示 1.分页插件使用:bootstarp-paginator.js,需要先引入bootstarp.js和jquery.js等: !function($){"u ...