AVL-平衡二叉树的原理和实现
一、简介
本文将通过图解和代码详细讲解AVL平衡二叉树的性质及失衡和再平衡的内容。在看本文之前希望大家具备二分搜索树的相关知识。或移步《二分搜索树》了解二分搜索树。
二、平衡二叉树
前面关于二分搜索树的文章,最后分析了在极端情况下,二分搜索树会退化为一个链表,那为了避免这种情况的发生,AVL平衡二叉树应运而生。
平衡二叉树的定义:
平衡二叉树是一颗二分搜索树,及平衡二叉树满足二分搜索树的所有性质
平衡二叉树要求任意一个节点的左右子树的高度差不能超过1
对于第一点应该挺容易理解的,对于第二点,我们要做一点解释。对于高度差,有一个专有名词平衡因子。
平衡因子:左子树的高度减去右子树的高度,及B = B左 - B右。由平衡二叉树的定义可知,平衡因子的取值只可能为0,1,-1。0:左右子树等高。1:左子树比较高。-1:右子树比较高。如下图
高度:一般的我们取叶子节点的高度值为1,任意一个节点的高度值取左右子树比较高的那个孩子节点的高度值然后加1。比如上图1中20这个节点的高度值,显然左子树比较高,所以H20 = H10 + 1;依次类推,H10 = H6(或者H14) + 1 = 2;所以H20 = 3;
上图1的树的各个节点的高度,如下图1所示。各个节点的平衡因子如下图2红色数字所示。所以根据定义,各个节点的左右子树的高度差不能超过1,及任意一个节点的平衡因子应该为 -1, 0, 1;所以下面的这棵树是一棵平衡二叉树。
三、AVL的失衡及再平衡--旋转
3.1、AVL的失衡情况
前面我们介绍了AVL的高度和平衡因子问题,接下来我们来看看有几种情况会导致AVL的失衡,也就是什么情况下我们需要调整AVL树,使其经过调整后能继续维持平衡状态。
如上图1所示,当我们已经插入了20,10元素,当我们再插入6这个元素的时候,很显然20节点的平衡因子为2,及B20 = 2 > 1此时该树已经不平衡了。
如上图2所示,当我们已经插入了20,10元素,当我们再插入14这个元素的时候,很显然20节点的平衡因子为2,及B20 = 2 > 1此时该树已经不平衡了。
如上图3所示,当我们已经插入了20,29元素,当我们再插入33这个元素的时候,很显然20节点的平衡因子为-2,及B20 = -2 < -1此时该树已经不平衡了。
如上图4所示,当我们已经插入了20,29元素,当我们再插入25这个元素的时候,很显然20节点的平衡因子为-2,及B20 = -2 < -1此时该树已经不平衡了。
对于AVL,需要进行再平衡操作的情况正如以上4个图所示。那接下来我们需要讨论的问题就是如何调整了,及旋转。
3.2、AVL的再平衡--旋转
如下图所示,对于上一章我们说的四种失衡状态的调整。然后我们加下来对照着下面的四张图片进行逐一介绍旋转的过程。
3.2.1、LL旋转
LL,及对于上图1的待旋转的树(中间那棵),造成这棵树失衡的节点6在20节点的左孩子的左孩子处,left,left简称为LL。这个时候我们需要进行的旋转一般称之为LL旋转。对于这种情况,我们考虑如何旋转,始终要考虑如何通过旋转既达到再平衡的目的,又能维持平衡二叉树的性质不变,即左孩子 < 父节点 < 右孩子。观察图一中插入节点6以后,一个很显然的结果就是不管我们怎么旋转只有当10节点在中间的时候我们才能保证这棵树是平衡的。我们知道了结果,再看看这个旋转的过程,为了保证平衡二叉树的性质,根据左孩子 < 父节点 < 右孩子的性质,我们看20 > 10,也就是说,我们可以将20节点下移,放到10节点的右孩子处,即得到图1中的结果。
为了更好地描述这个过程,我们使用几个虚拟的节点。
///////////////////////////////////////////////////
// LL T1<Z<T2< X <T3<Y<T4 //
// y x //
// / \ / \ //
// x T4 向右旋转 (y) z y //
// / \ - - - - - - - -> / \ / \ //
// z T3 T1 T2 T3 T4 //
// / \ //
// T1 T2 //
///////////////////////////////////////////////////
如上所示,我们真实的三个节点为Y > X > Z。然后我们为了方便描述,增加几个虚拟的节点,节点间的大小关系:T1<Z<T2< X <T3<Y<T4
对于LL,我们要右旋才能达到再平衡,根据之前描述,我们需要将Y节点顶替T3的位置,问题来了,T3放哪呢?根据大小关系 X < T3 < Y。我们可以将T3放到Y的左孩子节点的位置,这样进行旋转后得到的结果如上所示。我们发现这棵树不但达到了再平衡的目的,节点间的大小关系,依然维持了:T1<Z<T2< X <T3<Y<T4的关系。
代码实现一下这个过程,先假设我们的节点为Node。传入的参数应该是Y节点
private Node rightRotate(Node y) {
Node x = y.left;
Node T3 = x.right; // 向右旋转过程
x.right = y;
y.left = T3; return x;
}
对于以上代码,结合上面我们分析的过程,大家应该很容易就能理解。
3.2.2、RR旋转
RR对应上面的图3,RR及造成AVL失衡的节点6在20节点的右侧的右侧,即RR。对于RR我们要进行左旋转才能实现再平衡。同样的,我们如果想通过旋转达到再平衡,AVL树的性质依然是我们实现这个操作的根本。如上图3所示,如果我们将20节点移到29元素的左孩子节点处,便可实现再平衡。而且也能维持AVL树的基本性质。
同分析LL一样,我们增加一些虚拟节点来描述这个过程。
////////////////////////////////////////////////
// RR T1<Y<T2< X <T3<Z<T4 //
// y x //
// / \ / \ //
// T1 x 向左旋转 (y) y z //
// / \ - - - - - - - -> / \ / \ //
// T2 z T1 T2 T3 T4 //
// / \ //
// T3 T4 //
////////////////////////////////////////////////
节点间的大小关系:T1<Y<T2< X <T3<Z<T4。对于RR我们对Y节点进行左旋转。即让Y节点顶替T2,然后根据大小关系:Y < X < T2可知,我们可以将T2放到Y的右孩子节点处即可。对Y节点左旋完了如上图所示的结果。通过比较,节点间的大小关系,依然为:T1<Y<T2< X <T3<Z<T4。通过对Y节点的左旋转,达到了AVL的再平衡,并维持了AVL的性质不变。
代码实现就不解释了
private Node leftRotate(Node y) {
Node x = y.right;
Node T2 = x.left; // 向左旋转过程
x.left = y;
y.right = T2; return x;
}
3.2.3、LR
LR对应上图2,即造成AVL失衡的节点14在节点20的左侧的右侧,即LR。这种情况有点复杂,而且有个很想当然的坑,就是将根节点直接换成10不就完事了?可是如果我们这么做,发现,10的左节点为14,不满足:左孩子 < 父节点 < 右孩子的大小关系了。这种情况呢,正确的做法是先将10节点左旋,然后再将14节点右旋。大家通过之前对LL和RR的分析,在脑子中能不能想象到这个画面呢?
为了方便描述,我们依然增加一些虚假的节点来描述这个过程。
//////////////////////////////////////////////////////////////////////////////////////////
// LR T1<X<T2< Z <T3<Y<T4 //
// y y z //
// / \ / \ / \ //
// x t4 向左旋转(x) z T4 向右旋转(y) x y //
// / \ ---------------> / \ ---------------> / \ / \ //
// T1 z x T3 T1 T2 T3 T4 //
// / \ / \ //
// T2 T3 T1 T2 //
//////////////////////////////////////////////////////////////////////////////////////////
对于原始的这棵树呢,大小关系:T1<X<T2< Z <T3<Y<T4。如果我们先不看Y节点,看X,Z,T3节点,是不是可以发现,这正是我们上面描述的RR的情况啊。对RR,我们上面已经进行了详细的分析,通过贵X节点进行左旋,得到中间那棵树。这时又一个神奇的事情发生了,这棵树的形状又变成了前面我们说的,LL的情况。那大家就清楚了,对Y节点进行右旋转即可。最终的结果如上第三棵树,达到了AVL的再平衡并依然满足:T1<X<T2< Z <T3<Y<T4。
我们发现经过我们的分析,将这种复杂的情况进行一步步的拆解即分解成了比较简单的情况。不得不感叹一下:计算机的世界太神奇了。
3.2.4、RL
呃呵,自己看吧,不解释,不接受反驳。皮一下,很开心。
//////////////////////////////////////////////////////////////////////////////////////////
// RL: T1<Y<T2< Z <T3<X<T4 //
// y y z //
// / \ / \ / \ //
// T1 x 向右旋转(x) T1 z 向左旋转(y) y x //
// / \ - - - - - - -> / \ - - - - - - - - -> / \ / \ //
// z T4 T2 x T1 T2 T3 T4 //
// / \ / \ //
// T2 T3 T3 T4 //
//////////////////////////////////////////////////////////////////////////////////////////
相信大家看到这里,被面试官虐千百遍的问题,原来不过如此。其实一切高大上的问题,只要我们耐心的看下去就能有收获。
3.3、再平衡的时机
首先需要明白,AVL的失衡是由于节点的变动引起的,也就是增和删操作才会导致节点的变动。下面我们结合平衡因子和插入或者删除的过程,分析AVL再平衡的时机。
增加操作再平衡时机:
对于3.2章节中的过程,希望大家可以清楚,我们是通过眼睛观察来判断AVL是不是失衡了,但是计算机还没有达到这种能力。所以我们想想前面介绍的平衡因子,正是判断AVL是不是平衡的重要依据。假如现在向一棵空的AVL树依次插入[20,10,6];三个节点。当插入6节点后,如下图所示,各个节点的高度。之前说过:B = H左 - H右。我们看一下20这个节点的平衡因子,B20 = 2 - 0 = 2 > 1;所以,这时20就是不平衡的节点,需要对20这个节点进行旋转才能再平衡。但是从元素插入操作看一下,很显然当插入6这个元素的时候,并不知道20这个节点的平衡因子已经不满足要求了。需要沿着添加的元素向上回溯,沿着该节点到根节点的路径,一步步的重新计算其父节点,爷爷节点,祖父节点...当我们发现其父节点,爷爷节点...等平衡因子不满足要求的时候,就对该节点进行旋转。
删除操作再平衡的时机:
删除操作进行再平衡的时机类似增加操作,需要在删除节点后沿着其父节点,爷爷节点...一直向上计算各个节点的平衡因子是否满足AVL的性质。当发现某个节点的平衡因子不在[-1, 1]之间的时候,然后判断其形状对应的进行左旋转或者右旋转使其完成再平衡。
四、代码实现一棵AVL
在前面的章节详细介绍了AVL的定义,失衡,再平衡即LL,RR,LR,RL等旋转。接下来我们通过代码实现一棵AVL树。对于我们要实现的AVL平衡二叉树,我们期待具备的功能如下:
以Node作为链表的基础存储结构
使用泛型,并要求该泛型必须实现Comparable接口
基本操作:增删改查
4.1、AVL的基础代码
/**
* 描述:AVL 平衡二叉树的实现
*
* @Author shf
* @Date 2019/7/31 15:35
* @Version V1.0
**/
public class AVL<K extends Comparable<K>, V> { private class Node{
public K key;
public V value;
public Node left, right;
public int height;// 记录节点的高度 public Node(K key, V value){
this.key = key;
this.value = value;
left = null;
right = null;
height = 1;
}
} private Node root;
private int size; public AVL(){
root = null;
size = 0;
} public int getSize(){
return size;
} public boolean isEmpty(){
return size == 0;
}
}
在实现增删改查之前我们先设计两个辅助方法,如下所示,getHeight方法获取节点的高度值,getBalanceFactor方法获取节点的平衡因子。
/**
* 获得节点node的高度
* @param node
* @return
*/
private int getHeight(Node node){
if(node == null)
return 0;
return node.height;
} /**
* 获得节点node的平衡因子
* @param node
* @return
*/
private int getBalanceFactor(Node node){
if(node == null)
return 0;
return getHeight(node.left) - getHeight(node.right);
}
4.2、增
在《二分搜索树》介绍了二分搜索树,前面根据AVL的定义可知,AVL是完全满足一个二分搜索树的所有性质的,如果大家想搞明白AVL,还是建议去先去看一下二分搜索树。对于二分搜索树的添加操作的代码实现,如下所示:
/**
* 添加元素
* @param e
*/
public void add(E e){
root = add(root, e);
} /**
* 添加元素 - 递归实现
* 时间复杂度 O(log n)
* @param node
* @param e
* @return 返回根节点
*/
public Node add(Node node, E e){
if(node == null){// 如果当前节点为空,则将要添加的节点放到当前节点处
size ++;
return new Node(e);
}
if(e.compareTo(node.e) < 0){// 如果小于当前节点,递归左孩子
node.left = add(node.left, e);
} else if(e.compareTo(node.e) > 0){// 如果大于当前节点,递归右孩子
node.right = add(node.right, e);
}
return node;
}
如果你还没法理解上面的代码,请移步《二分搜索树》。对于AVL的添加操作,无非就是在AVL中需要考虑在二分搜索树失衡的时候,如何通过旋转达到再平衡。根据我们前面的介绍,我们应该已经很明白旋转的思路了,那我们直接上代码吧。
/**
* 向以node为根的二分搜索树中插入元素(key, value),递归算法
* 时间复杂度 O(log n)
* @param node
* @param key
* @param value
* @return 返回插入新节点后二分搜索树的根
*/
private Node add(Node node, K key, V value){ if(node == null){
size ++;
return new Node(key, value);
} if(key.compareTo(node.key) < 0)
node.left = add(node.left, key, value);
else if(key.compareTo(node.key) > 0)
node.right = add(node.right, key, value);
else // key.compareTo(node.key) == 0
node.value = value; // 更新height
node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right)); // 计算平衡因子
int balanceFactor = getBalanceFactor(node); // 平衡维护
//////////////////////////////////////////////////////
// LL T1<Z<T2< X <T3<Y<T4 //
// y x //
// / \ / \ //
// x T4 向右旋转 (y) z y //
// / \ - - - - - - - -> / \ / \ //
// z T3 T1 T2 T3 T4 //
// / \ //
// T1 T2 //
//////////////////////////////////////////////////////
if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0)
return rightRotate(node);
//////////////////////////////////////////////////////////////////////////////////////////
// LR T1<X<T2< Z <T3<Y<T4 //
// y y z //
// / \ / \ / \ //
// x t4 向左旋转(x) z T4 向右旋转(y) x y //
// / \ ---------------> / \ ---------------> / \ / \ //
// T1 z x T3 T1 T2 T3 T4 //
// / \ / \ //
// T2 T3 T1 T2 //
//////////////////////////////////////////////////////////////////////////////////////////
if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
node.left = leftRotate(node.left);
return rightRotate(node);
}
//////////////////////////////////////////////////
// RR: T1<Y<T2< X <T3<Z<T4 //
// y x //
// / \ / \ //
// T1 x 向左旋转 (y) y z //
// / \ - - - - - - - -> / \ / \ //
// T2 z T1 T2 T3 T4 //
// / \ //
// T3 T4 //
//////////////////////////////////////////////////
if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0)
return leftRotate(node); //////////////////////////////////////////////////////////////////////////////////////////
// RL: T1<Y<T2< Z <T3<X<T4 //
// y y z //
// / \ / \ / \ //
// T1 x 向右旋转(x) T1 z 向左旋转(y) y x //
// / \ - - - - - - -> / \ - - - - - - - - -> / \ / \ //
// z T4 T2 x T1 T2 T3 T4 //
// / \ / \ //
// T2 T3 T3 T4 //
//////////////////////////////////////////////////////////////////////////////////////////
if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
node.right = rightRotate(node.right);
return leftRotate(node);
} return node;
}
看上面代码,辅之旋转示意图和平衡因子,相信大家能通过自己的分析,根据当前节点和左右孩子的平衡因子能判断出来是LL,LR,RR,或者RL的情况。
4.3、左旋和右旋
至于左右旋转,我们前文给出了代码,但当我们对节点进行旋转以后,我们需要重新维护一下各个节点的高度值。所以经过完善的左右旋转的代码如下:
/**
* 对节点y进行向右旋转操作,返回旋转后新的根节点x
* @param y
* @return
*/
///////////////////////////////////////////////////
// LL T1<Z<T2< X <T3<Y<T4 //
// y x //
// / \ / \ //
// x T4 向右旋转 (y) z y //
// / \ - - - - - - - -> / \ / \ //
// z T3 T1 T2 T3 T4 //
// / \ //
// T1 T2 //
///////////////////////////////////////////////////
private Node rightRotate(Node y) {
Node x = y.left;
Node T3 = x.right; // 向右旋转过程
x.right = y;
y.left = T3; // 更新height
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1; return x;
} /**
* 对节点y进行向左旋转操作,返回旋转后新的根节点x
* @param y
* @return
*/
////////////////////////////////////////////////
// RR T1<Y<T2< X <T3<Z<T4 //
// y x //
// / \ / \ //
// T1 x 向左旋转 (y) y z //
// / \ - - - - - - - -> / \ / \ //
// T2 z T1 T2 T3 T4 //
// / \ //
// T3 T4 //
////////////////////////////////////////////////
private Node leftRotate(Node y) {
Node x = y.right;
Node T2 = x.left; // 向左旋转过程
x.left = y;
y.right = T2; // 更新height
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1; return x;
}
4.4、删
对于删除,稍微复杂一点,但是基本思路和增加是一样的。但是关于我觉得还是有必要给大家恶补一下二分搜索树的删除的思路,当我们删除一个节点的时候,待删除节点左右子树有一个为空,我们只需要将其不为空的子树的根节点提到待删除元素的位置即可。如果其左右子树都不为空,则将其右子树最小的元素提到待删除节点处。详细的讨论请参阅《二分搜索树》,在二分搜索树删除操作的基础上,我们只需要辅之再平衡操作即可。
/**
* 从二分搜索树中删除键为key的节点
* @param key
* @return
*/
public V remove(K key){ Node node = getNode(root, key);
if(node != null){
root = remove(root, key);
return node.value;
}
return null;
} /**
* 删除指定的节点
* @param node
* @param key
* @return
*/
private Node remove(Node node, K key){ if( node == null )
return null; Node retNode;
if( key.compareTo(node.key) < 0 ){
node.left = remove(node.left , key);
// return node;
retNode = node;
}
else if(key.compareTo(node.key) > 0 ){
node.right = remove(node.right, key);
// return node;
retNode = node;
}
else{ // key.compareTo(node.key) == 0 找到待删除的节点 node // 待删除节点左子树为空,直接将右孩子替代当前节点
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
// return rightNode;
retNode = rightNode;
} // 待删除节点右子树为空,直接将左孩子替代当前节点
else if(node.right == null){
Node leftNode = node.left;
node.left = null;
size --;
// return leftNode;
retNode = leftNode;
} // 待删除节点左右子树均不为空的情况
else{
// 待删除节点左右子树均不为空
// 找到右子树最小的元素,替代待删除节点
Node successor = minimum(node.right);
//successor.right = removeMin(node.right);
successor.right = remove(node.right, successor.key);
successor.left = node.left; node.left = node.right = null; // return successor;
retNode = successor;
}
} if(retNode == null)
return null; // 更新height
retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right)); // 计算平衡因子
int balanceFactor = getBalanceFactor(retNode); // 平衡维护
// LL
if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0)
return rightRotate(retNode); // RR
if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0)
return leftRotate(retNode); // LR
if (balanceFactor > 1 && getBalanceFactor(retNode.left) < 0) {
retNode.left = leftRotate(retNode.left);
return rightRotate(retNode);
} // RL
if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) {
retNode.right = rightRotate(retNode.right);
return leftRotate(retNode);
} return retNode;
}
/**
* 返回以node为根的二分搜索树的最小值所在的节点
* @param node
* @return
*/
private Node minimum(Node node){
if(node.left == null)
return node;
return minimum(node.left);
}
4.5、其他操作
/**
* 返回以node为根节点的二分搜索树中,key所在的节点
* @param node
* @param key
* @return
*/
private Node getNode(Node node, K key){ if(node == null)
return null; if(key.equals(node.key))
return node;
else if(key.compareTo(node.key) < 0)
return getNode(node.left, key);
else // if(key.compareTo(node.key) > 0)
return getNode(node.right, key);
} /**
* 判断是否包含 key
* @param key
* @return
*/
public boolean contains(K key){
return getNode(root, key) != null;
} /**
* 获取指定 key 的 value
* @param key
* @return
*/
public V get(K key){ Node node = getNode(root, key);
return node == null ? null : node.value;
} /**
* 设置 key 对应元素的值 value
* @param key
* @param newValue
*/
public void set(K key, V newValue){
Node node = getNode(root, key);
if(node == null)
throw new IllegalArgumentException(key + " doesn't exist!"); node.value = newValue;
}
五、验证AVL
前面我们实现了一棵AVL树,我们如何验证这到底是不是一棵AVL树呢?这个问题,我们依然是从其定义来思考,首先,AVL是一棵二分搜索树,其次每个节点的平衡因子能满足在[-1, 1]之间,能满足这两点其实加之我们代码的逻辑即可判断其是不是一棵AVL树了。
二分搜索树有一个延伸出来的性质不知道大家还记不记得,对于二分搜索树的中序遍历,其实是对二分搜索树从小到大排序的过程。那我们判断中序遍历的结果满不满足从小到大即可判定其是不是一棵二分搜索树。
/**
* 测试方法 - 判断该二叉树是否是一棵二分搜索树
* @return
*/
public boolean isBST(){ ArrayList<K> keys = new ArrayList<>();
inOrder(root, keys);
for(int i = 1 ; i < keys.size() ; i ++)
if(keys.get(i - 1).compareTo(keys.get(i)) > 0)
return false;
return true;
} /**
* 中序遍历
* @param node
* @param keys
*/
private void inOrder(Node node, ArrayList<K> keys){ if(node == null)
return; inOrder(node.left, keys);
keys.add(node.key);
inOrder(node.right, keys);
} /**
* 测试方法 - 判断该二叉树是否是一棵平衡二叉树
* @return
*/
public boolean isBalanced(){
return isBalanced(root);
} /**
* 判断以Node为根的二叉树是否是一棵平衡二叉树,递归算法
* @param node
* @return
*/
private boolean isBalanced(Node node){ if(node == null)
return true; int balanceFactor = getBalanceFactor(node);
if(Math.abs(balanceFactor) > 1)
return false;
return isBalanced(node.left) && isBalanced(node.right);
}
我们写如下测试代码:
public class Main {
public static void main(String[] args) {
AVL<Integer, Integer> avl = new AVL<>();
for (int i=0; i< 10; i++){
avl.add(i, i);
}
System.out.println(avl.isBST());
System.out.println(avl.isBalanced());
avl.remove(5);
System.out.println(avl.isBST());
System.out.println(avl.isBalanced());
}
}
true
true
true
true
到此,AVL所有的内容我们已经介绍完了。哎呦,凌晨三点了,心疼自己一秒钟。我爱我的国。
参考文献:
《玩转数据结构-从入门到进阶-刘宇波》
《数据结构与算法分析-Java语言描述》
如有错误的地方还请留言指正。
原创不易,转载请注明原文地址:https://www.cnblogs.com/hello-shf/p/11352071.html
AVL-平衡二叉树的原理和实现的更多相关文章
- AVL平衡二叉树实现,图解分析,C++描述,完整可执行代码
body, table{font-family: 微软雅黑; font-size: 13.5pt} table{border-collapse: collapse; border: solid gra ...
- 3.1 C语言_实现AVL平衡二叉树
[序] 上节我们实现了数据结构中最简单的Vector,那么来到第三章,我们需要实现一个Set set的特点是 内部有序且有唯一元素值:同时各种操作的期望操作时间复杂度在O(n·logn): 那么标准的 ...
- 数据结构中很常见的各种树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)
数据结构中常见的树(BST二叉搜索树.AVL平衡二叉树.RBT红黑树.B-树.B+树.B*树) 二叉排序树.平衡树.红黑树 红黑树----第四篇:一步一图一代码,一定要让你真正彻底明白红黑树 --- ...
- 查找(AVL平衡二叉树)
[1]为什么需要平衡二叉树? 矛盾是推进事物向前发展的源动力. 那么平衡二叉树是从哪里来?肯定是有矛盾存在的.请看程来师的分析: [2]什么是平衡二叉树? 平衡二叉树的基本认识: [3]平衡二叉树的构 ...
- 数据结构中常见的树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)
树 即二叉搜索树: 1.所有非叶子结点至多拥有两个儿子(Left和Right): 2.所有结点存储一个关键字: 非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树: 如: BST树 ...
- AVL平衡二叉树
AVL树 1.若它的左子树不为空,则左子树上所有的节点值都小于它的根节点值. 2.若它的右子树不为空,则右子树上所有的节点值均大于它的根节点值. 3.它的左右子树也分别可以充当为二叉查找树. 例如: ...
- PAT A1123 Is It a Complete AVL Tree (30 分)——AVL平衡二叉树,完全二叉树
An AVL tree is a self-balancing binary search tree. In an AVL tree, the heights of the two child sub ...
- AVL平衡二叉树实现
#include<stdio.h> #include<stdlib.h> #define TRUE 1 #define FALSE 0 #define EH 0 #define ...
- AVL平衡二叉树的各种问题(Balanced Binary Tree)
AVL树或者是一棵空树,或者是具有以下性质的非空二叉搜索树: 1. 任一结点的左.右子树均为AVL树: 2.根结点左.右子树高度差的绝对值不超过1. 1.声明 #include<iostream ...
- [树结构]平衡二叉树AVL
平衡二叉树是一种二叉排序树,其中每一个节点的左子树和右子树的高度至多等于1,平衡二叉树又称为AVL树. 将二叉树节点的左子树深度减去右子树深度的值称为平衡因子BF,平衡二叉树上所有节点的平衡因子只可能 ...
随机推荐
- 推荐:经典SQL语句大全
一.基础 .说明:备份sql server--- 创建 备份数据的 device USE master EXEC sp_addumpdevice 'disk', 'testBack', 'c:/mss ...
- Spring还可以这样用缓存,你知道吗?
大家在项目开发过程中,或多或少都用过缓存,为了减少数据库的压力,把数据放在缓存当中,当访问的请求过来时,直接从缓存读取.缓存一般都是基于内存的,读取速度比较快,市面上比较常见的缓存有:memcache ...
- .NET Core CSharp初级篇 类的生命历程
.NET Core CSharp初级篇 1-7 本节内容为类的生命周期 引言 对象究竟是一个什么东西?对于许多初学者而言,对象都是一个非常抽象的知识点.如果非要用一句话描述,我觉得"万物皆对 ...
- Pivotal:15分钟部署你的应用
“ 本篇文章介绍的是PaaS平台Pivotal Cloud Foundry(以下简称PCF)的初步使用,相比于传统的IaaS平台(比如阿里云),PCF可实现快速迭代开发与部署,让您专注于业务开发.” ...
- java反射原理及Class应用
反射:框架设计灵魂 框架:半成品软件,可以在框架基础上进行软件开发,简化编码 反射:将类的各个组成部分封装我其他对象,这就是反射机制 好处: 1.可以在程序运行过程中,操作这些对象 2.可以解耦, ...
- 【MySQL】Illegal mix of collations (utf8mb4_general_ci,IMPLICIT) and ...
线上遇到这个问题,详细信息如下: SQL state [HY000]; error code [1267]; Illegal mix of collations (utf8mb4_general_ci ...
- 【Maven】Mac 使用 zsh 后 mvn 命令就无效
RT -- 解决方法: 打开 .zshrc 文件,将 Maven 环境变量配置加入其中,或者 将 source ~/.bash_profile 添加到 .zshrc 中. PS: 之前搞不懂,每次使用 ...
- 【Python】Django 的邮件引擎用法详解!!(调用163邮箱为例)
1. send_mall()方法介绍 位置: 在django.core.mail模块提供了send_mail()来发送邮件. 方法参数: send_mail(subject, message, fro ...
- 优雅的对象转换解决方案-MapStruct使用进阶(二)
在前面, 介绍了 MapStruct 及其入门. 本文则是进一步的进阶. 在 MapStruct 生成对应的实现类的时候, 有如下的几个情景. 1 属性名称相同,则进行转化 在实现类的时候, 如果属性 ...
- Linux服务部署Yapi项目(安装Node Mongdb Git Nginx等)
Linux服务部署Yapi 一,介绍与需求 1,我的安装环境:CentOS7+Node10.13.0+MongoDB4.0.10. 2,首先安装wget,用于下载node等其他工具 yum insta ...