二叉搜索树(BST)和二叉平衡树(AVL)
二叉搜索树(BST)
在二叉搜索树(又称二叉排序树或者二叉查找树)中:
- 若任意结点的左子树不空,则左子树上所有结点的值均不大于它的根结点的值;
- 若任意结点的右子树不空,则右子树上所有结点的值均不小于它的根结点的值;
- 任意结点的左、右子树也分别为二叉搜索树。
基本操作和代码实现
树的节点
class Node {
Integer data;
Node left;
Node right;
Node parent;
public Node(Integer data, Node left, Node right, Node parent) {
this.data = data;
this.left = left;
this.right = right;
this.parent = parent;
}
}
最小关键字
最小关键字查询过程:根据二叉搜索树性质,左子树小于该节点。从根节点开始,一直找left指针,直到left指针为null。
public Node getMin(Node root) {
if (root == null) {
return null;
}
while (root.left != null) {
root = root.left;
}
return root;
}
最大关键字
最大关键字查询过程:根据二叉搜索树性质,右子树大于该节点。从根节点开始,一直找right指针,直到right指针为null。
public Node getMax(Node root) {
if (root == null) {
return null;
}
while (root.right != null) {
root = root.right;
}
return root;
}
查找
查找某关键字的过程为:
- 树根开始查找,若根为null,则查找失败,返回NULL;
- 如果根节点的值等于关键字,返回根节点,结束查找;
- 如果根节点的值小于关键字,继续查找根节点的左子树;
- 如果根节点的值大于关键字,继续查找根节点的右子树;
- 如果查找到叶子节点仍不相等,关键字不存在,返回NULL,结束查找。
/**
* 查找值为data的节点(递归)
* @param root
* @param data
* @return
*/
public Node searchRec(Node root, int data) {
if (root == null) {
return null;
}
if (root.data > data) {
return searchRec(root.left, data);
} else if (root.data < data) {
return searchRec(root.right, data);
} else {
return root;
}
}
/**
* 查找值为data的节点
* @param root
* @param data
* @return
*/
public Node search(Node root, int data) {
while (root != null) {
if (root.data > data) {
root = root.left;
} else if (root.data < data) {
root = root.right;
} else {
break;
}
}
return root;
}
插入节点
插入节点的过程:
- 新值v插入到一棵二叉搜索树T中,新建一个结点tmp;
- 从根节点开始,如果值v大于根节点,查找其右子树;
- 如果值v小于根节点,查找其左子树;
- 当某个节点的左子树或右子树为null时,这个null的位置即为要插入节点tmp的位置。
- 如果在查找过程中,发现值v等于某个节点的值,证明该值已经存在于树中,插入结束。
/**
* BST插入节点(递归)
*
* @param root
* @param insert
* @return
*/
public Node insertRec(Node root, Node insert) {
if (root == null) {
root = insert;
} else if (root.data > insert.data) {
root.left = insertRec(root.left, insert);
} else if (root.data < insert.data) {
root.right = insertRec(root.right, insert);
}
return root;
}
/**
* BST插入节点
*
* @param root
* @param insert
* @return
*/
public Node indert(Node root, Node insert) {
if (root == null) {
root = insert;
} else {
Node tmp = root;
Node parent = null;//记录父节点
while (tmp != null) {//定位插入的位置
parent = tmp;
if (insert.data > tmp.data) {
tmp = tmp.right;
} else {
tmp = tmp.left;
}
}
insert.parent = parent;
if (parent.data > insert.data) {
parent.left = insert;
} else if (parent.data < insert.data) {
parent.right = insert;
}
//如果 parent.data = insert.data,说明该节点已经在树中,不用重新插入
}
return root;
}
在插入新结点后,新结点总是作为一个新叶子结点而存在的。
删除某个节点
删除和插入一样,会引起树的变化。要保证不破坏二叉搜索树的性质,删除后需要做一些调整。
寻找要删除的节点的过程和查询过程类似。找到的节点有一下情况:
-
为叶子节点,待删除的节点没有子节点。直接删除该节点,并修改父节点指向该节点的指针为null。
-
待删除的节点只有左孩子。
2.1 如果该节点为父节点的左孩子,将父节点的左指针指向该节点的左孩子即可;
2.2 如果该节点为父节点的右孩子,将父节点的右指针指向该节点的左孩子即可;
-
待删除的节点只有右孩子。
3.1 如果该节点为父节点的左孩子,将父节点的左指针指向该节点的右孩子即可;
3.2 如果该节点为父节点的右孩子,将父节点的右指针指向该节点的右孩子即可;
-
待删除的节点有左孩子和右孩子。
过程:
a. 找到右子树的最小值;
b. 将右子树的最小值的数据赋值给待删除节点的数据;
c. 然后删除最小值位置的那个节点。
public void delete(Node root, Node del) {
if (root == null) {
return;
}
Node tmp = null;
while (root != null) {//定位需要删除的节点
if (root.data > del.data) {
tmp = root;//记录父节点
root = root.left;
} else if (root.data < del.data) {
tmp = root;//记录父节点
root = root.right;
} else {//此时。root即位要删除的节点
if (root.left == null && root.right == null) {//<1>待删除的节点没有子节点
if (tmp == null) {//删除的为树的根节点
root = null;
} else {
if (tmp.left == root) {
tmp.left = null;
} else if (tmp.right == root) {
tmp.right = null;
}
}
} else if (root.right == null && root.left != null) {//<2>待删除的节点只有左孩子
if (tmp == null) {//删除的为树的根节点
root = root.left;
} else {
if (tmp.left == root) {//待删除的节点为父节点的左孩子
tmp.left = root.left;
} else if (tmp.right == root) {//待删除的节点为父节点的右孩子
tmp.right = root.left;
}
}
} else if (root.right != null && root.left == null) {//<3>待删除的节点只有右孩子
if (tmp == null) {//删除的为树的根节点
root = root.right;
} else {
if (tmp.right == root) {//待删除的节点为父节点的右孩子
tmp.right = root.right;
} else if (tmp.left == root) {//待删除的节点为父节点的左孩子
tmp.left = root.right;
}
}
} else {//<4>待删除的节点有左孩子和右孩子
/**
* 方法:
* a.找到右子树的最小值;
* b.将右子树的最小值的数据赋值给待删除节点的数据;
* c.然后删除最小值位置的那个节点。
*/
Node rightMin = getMin(root.right);//右子树的最小值的节点
Node rightMinParent = rightMin.parent;//右子树的最小值的节点的父节点
root.data = rightMin.data;
if (rightMinParent.left == rightMin) {
rightMinParent.left = null;
} else if (rightMinParent.right == rightMin) {
rightMinParent.right = null;
}
}
}
break;//删除之后结束循环
}
}
二叉平衡树(AVL)
二叉平衡树(AVL树),任意节点对应的两颗子树的最大高度差为1,因此它被称为高度平衡树。它或者是一棵空树,或者是具有下列性质的树:
- 具备二叉排序树的所有性质;
- 左子树和右子树深度差的绝对值不超过1;
- 左子树和右子树都是二叉平衡树
AVL树的查找、插入和删除在平均和最坏情况下都是O(logn)。
基本操作和代码实现
AVL树的节点
- data为节点的值
- left节点的左孩子
- right节点的右孩子
- Height高度
class Node<T> {
T data;
Node<T> left;
Node<T> right;
int height;
public Node(T data, Node<T> left, Node<T> right) {
this.data = data;
this.left = left;
this.right = right;
this.height = 1;//初始话高度为1,当树为空时,高度为0。
}
}
树的高度
树的高度为最大层次。即空的二叉树的高度是0,非空树的高度等于它的最大层次。
/**
* 获取树的高度
* @param root
* @return
*/
public int height(Node root) {
if (root != null) {
return root.height;
} else {
return 0;
}
}
旋转
在AVL树进行插入或者删除操作后,可能导致AVL树失去平衡(左子树的高度和右子树的高度相差大于1)。

上图中的四颗失去平衡的AVL树中,依次是:LL、LR、RL、RR。
LL:也称"左左",插入或删除一个节点后,根节点的左子树的左子树还有非空子节点,导致"根的左子树的高度"比"根的右子树的高度"大2,导致AVL树失去了平衡。
LR:也称"左右",插入或删除一个节点后,根节点的左子树的右子树还有非空子节点,导致"根的左子树的高度"比"根的右子树的高度"大2,导致AVL树失去了平衡。
LR:也称"右左",插入或删除一个节点后,根节点的右子树的左子树还有非空子节点,导致"根的右子树的高度"比"根的左子树的高度"大2,导致AVL树失去了平衡。
RR:也称"右右",插入或删除一个节点后,根节点的右子树的右子树还有非空子节点,导致"根的右子树的高度"比"根的左子树的高度"大2,导致AVL树失去了平衡。
上述四种情况可采用对应的旋转方法来使其恢复平衡。
LL旋转

上图中左边的失去平衡的LL树,经过LL旋转变成了右边的AVL树。
LL旋转是围绕"失去平衡的AVL根节点"进行的,也就是上图中的"8",将"4"变为根节点,"8"变为"4"的右子树,"4"的右子树变为"8"的左子树。
public Node LLRotation(Node k2) {
//上图中8为k2,4为k1
Node k1 = k2.left;
k2.left = k1.right;
k1.right = k2;
k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
k1.height = Math.max(height(k1.left), k2.height) + 1;
return k1;
}
RR旋转

RR旋转是与LL旋转对称的情况。
public Node RRRotation(Node k1) {
//8为k1,12为k2
Node k2 = k1.right;
k1.right = k2.left;
k2.left = k1;
k1.height = Math.max(height(k1.left), height(k1.right)) + 1;
k2.height = Math.max(height(k2.right), k1.height) + 1;
return k2;
}
LR旋转
对于LR失衡的情况,要经过两次旋转才能让AVL树恢复平衡。

第一次旋转是围绕"4"进行RR旋转,第二次旋转是围绕"6"进行LL转转。
public Node LRRotation(Node k3){
//上图中4为k1,8为k3,6为k2
k3.left = RRRotation(k3.left);
return LLRotation(k3);
}
RL旋转
RL旋转与LR旋转的情况对称。

第一次旋转是围绕"12"的LL旋转,第二次旋转是围绕"10"的RR旋转。
public Node RLRotation(Node k1){
k1.right = LLRotation(k1.right);
return RRRotation(k1);
}
插入节点
将值data插入到树root中。
public Node insert(Node root, int data) {
if (root == null) {
return new Node(data, null, null);
} else {
if (data < root.data) {//插入左子树
root.left = insert(root.left, data);
if (height(root.left) - height(root.right) == 2) {//需要进行旋转调整
if (data < root.left.data) {
root = LLRotation(root);
} else {
root = LRRotation(root);
}
}
} else if (data > root.data) {//插入右子树
root.right = insert(root.right, data);
if (height(root.right) - height(root.left) == 2) {//需要进行旋转调整
if (data > root.right.data) {
root = RRRotation(root);
} else {
root = RLRotation(root);
}
}
} else {//重复添加
}
}
root.height = Math.max(height(root.left), height(root.right)) + 1;
return root;
}
删除节点
删除树中值为data的节点。
public Node delete(Node root, int data) {
if (root == null) {
return null;
}
if (data < root.data) {//待删除的节点在左子树中
root.left = delete(root.left, data);
if (height(root.right) - height(root.left) == 2) {//树失去平衡,需要调整
Node tmp = root.right;
if (tmp != null) {
if (height(tmp.left) > height(tmp.right)) {
root = RLRotation(root);
} else {
root = RRRotation(root);
}
}
}
} else if (data > root.data) {//待删除的节点在右子树中
root.right = delete(root.right, data);
Node tmp = root.left;
if (tmp != null) {
if (height(tmp.right) > height(tmp.left)) {
root = LRRotation(root);
} else {
root = LLRotation(root);
}
}
} else {//找到了待删除的节点
if (root.left == null && root.right == null) {//待删除的节点为叶子节点
root = null;
} else if (root.left != null && root.right == null) {//待删除的节点只有左子树
root = root.left;
root.height = Math.max(height(root.left), height(root.right)) + 1;
} else if (root.left == null && root.right != null) {//待删除的节点只有右子树
root = root.right;
root.height = Math.max(height(root.right), height(root.left)) + 1;
} else {//待删除待节点左右子树均不为null
Node leftMax = root.left;
while (leftMax.right != null) {//左子树中的最大值
leftMax = leftMax.right;
}
//将该最大节点的值赋值给root
root.data = leftMax.data;
//删除该最大节点
root.left = delete(root.left, leftMax.data);
}
}
return root;
}
本文详细介绍了二叉搜索树(BST)的基本操作,包括查找、插入和删除节点,以及二叉平衡树(AVL树)的概念,重点讨论了AVL树的高度平衡性质和旋转操作,如LL、RR、LR、RL旋转,确保查找、插入和删除的高效性。

被折叠的 条评论
为什么被折叠?



