二叉查找树---红黑树基础
Posted 后端开发与算法
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉查找树---红黑树基础相关的知识,希望对你有一定的参考价值。
二叉查找树
定义:一颗二叉查找树(BST)是一颗二叉树,其中每个结点都含有一个Comparable的键(以及相关联的值)且每个节点的键都大于其左子树中的任意结点的键而小于右子树任意的结点。
示例:
基本实现
数据表示,和链表一样,我们嵌套定义了一个私有类来表示二叉查找树上的一个结点。每个结点都有一个键、一个值(这里为了简便省略了)、一条左链接、一条右链接和一条计数器(有时候我们需要将途中每个结点计数器的值记在上方,这里没有做实现,有兴趣的可以自行增加)。左链接指向一颗由小于该节点所有键组成的二叉查找树,右链接指向一颗由大于该结点的所有键组成的二叉查找树。
查找
1.如果当前值等于p节点的值直接返回
2.如果当前值大于p节点的值则向右子节点查找
3.如果当前值小于p节点的值则向左子节点查找
/**
* 查询方法
* 1.如果当前值等于p节点的值直接返回
* 2.如果当前值大于p节点的值则向右子节点查找
* 3.如果当前值小于p节点的值则向左子节点查找
* @param t
* @return
*/
public Node find(int t){
Node p = tree;
while (p != null){
if (p.data == t){
return p;
}else{
if (p.data < t){
p = p.right;
}else{
p = p.left;
}
}
}
return null;
}
插入
1.判断根节点是否为空 如果为空则直接赋值
2.判断是否大于当前节点 如果大于则判断右子节点,否则判断左子节点
3.如果子节点为null则直接插入 否则继续遍历
/**
* 插入方法
* 1.判断根节点是否为空 如果为空则直接赋值
* 2.判断是否大于当前节点 如果大于则判断右子节点,否则判断左子节点
* 3.如果子节点为null则直接插入 否则继续遍历
* @param t
*/
public void insert(int t){
if (null == tree){
tree = new Node(t);
return;
}
Node p = tree;
while (p != null){
// 如果当前值大于data则放在右子树
if (t > p.data){
if (p.right == null){
p.right = new Node(t);
return;
}
p = p.right;
}else{
// t < p.data
if (p.left == null){
p.left = new Node(t);
return;
}
p = p.left;
}
}
}
删除
1.删除主要是判断当前节点是否含有子节点
2.如果删除的节点不含有子节点 直接将该节点的父节点赋为null即可
3.删除的节点含有两个子节点,需要找到右子节点中最小的数据放到删除的位置
/**
* 删除方法
* 1.删除主要是判断当前节点是否含有子节点
* 2.如果删除的节点不含有子节点 直接将该节点的父节点赋为null即可
* 2.删除的节点含有两个子节点,需要找到右子节点中最小的数据放到删除的位置
* 4.如果删除的节点只包含一个子节点或者删除的是叶子结点
* @param t
*/
public void delete(int t){
// 删除节点和该节点的父节点
Node p = tree,pp = null;
while (p != null && p.data != t){
// 存储父节点的地址
pp = p;
if (p.data < t){
p = p.right;
} else {
p = p.left;
}
}
// 未找到
if (p == null) return;
// 1.删除的节点含有两个子节点,需要找到右子节点中最小的数据放到删除的位置
if (p.right != null && p.left != null){
// 寻找右节点中最小节点 t1为左节点 t2为父节点
Node t1 = p.right,t2 = p;
while (p.left != null){
t2 = t1;
t1 = t1.left;
}
// 替换左节点和删除节点的值
p.data = t1.data;
// 删除pp和p p为左节点 pp为父节点
pp = t2;
p = t1;
}
// 2.如果删除的节点只包含一个子节点或者删除的是叶子结点
// p的子节点
Node child;
if (p.left != null){
child = p.left;
}else if(p.right != null){
child = p.right;
}else{
child = null;
}
// 如果删除是根节点
if (pp == null){
tree = child;
}else if (pp.left == p){
pp.left = child;
}else{
pp.right = child;
}
}
二叉查找树的其他操作
查找最大最小键
/**
* 查找最小键
* 1.向左查找,如果当前节点的左节点为空则为最小节点 ,最大节点于此同理
* @return
*/
public Node min(){
return min(tree);
}
private Node min(Node node){
if (node.left == null) return node;
return min(node.left);
}
/**
* 查找最大键
* 1.向右查找,如果当前节点的右节点为空则为最大节点 ,最小节点于此同理
* @return
*/
public Node max(){
return max(tree);
}
private Node max(Node node){
if (node.right == null) return node;
return max(node.right);
}
向上向下取整
/**
* 向下取整
* 即取不大于x的最大整数
* (与“四舍五入”不同,下取整是直接取按照数轴上最接近要求值的左边值,即不大于要求值的最大的那个值)
* (向上取整于此同理)
* @param key
* @return
*/
public Node floor(int key){
return floor(tree,key);
}
/**
* 思路
* 1.如果给定的键key小于二叉查找树的根节点的键,那么小于等于key的最大键floor(key一定在根节点的左子树中)
* 2.如果给定的键key大于二叉查找树的根节点的键,那么只有当根节点右子树中存在小于等于key的结点时,小于等于key的最大键才存在于右子树中
* 否则根节点就是小于等于key的最大键。
* @param tree
* @param key
* @return
*/
private Node floor(Node tree, int key) {
if (tree == null) return null;
if (tree.data == key) return tree;
if (tree.data > key) return floor(tree.left,key);
Node floor = floor(tree.right, key);
if (floor != null) return floor;
return tree;
}
排名
/**
* 假设我们需要查找排名为k的结点,就需要实现select方法
* 1.如果左子树的结点数t大于k,那么我们就继续(递归地)在左子树中查找排名为k的键;如果t=k,那么我们就返回根节点中的键
* 2.如果t小于了k,我们就递归的从右子树中查找排名为k-t-1的键。
* @param key
* @return
*/
public Node select(int key){
return select(tree,key);
}
private Node select(Node tree, int k) {
if (tree == null) return null;
if (k == size(tree)){
return tree;
}else{
int size = size(tree);
if (k > size(tree)){
return select(tree.right,k - size - 1);
}else {
return select(tree.left,k);
}
}
}
二叉查找树除了以上操作以外,还有一个特别重要的特性就是中序遍历二叉查找树,可以输出有序的二叉序列,时间复杂度为O(n),因此二叉查找树又叫二叉排序树。
遍历复习
/**
* 5
* / \
* 2 10
* \ \
* 3 22
*/
// 前序遍历 5 2 3 10 22 先打印本身,在打印左节点,最后打印右节点
System.out.println(binarySearchTree.preorderPrint());
// 中序遍历 2 3 5 10 22 先打印左节点,再打印本身,最后打印右节点
System.out.println(binarySearchTree.inorderPrint());
// 后序遍历 3 2 22 10 5先打印左节点,再打印右节点,最后打印本身
System.out.println(binarySearchTree.postorderPrint());
二叉树的时间复杂度
二叉查找树中和有序性有关的操作效率如何?我们首先需要知道二叉查找树的高度(即树中任意结点的最大深度)。给定一颗树,树的高度决定了所有操作在最坏情况下的性能。
实际上,二叉查找树的形态各式各样。比如这个图中,对于同一组数据,我们构造了三种二叉查找树。它们的查找、插入、删除操作的执行效率都是不一样的。图中第一种二叉查找树,根节点的左右子树极度不平衡,已经退化成了链表,所以查找的时间复杂度就变成了 O(n)。
显然,极度不平衡的二叉查找树,它的查找性能肯定不能满足我们的需求。我们需要构建一种不管怎么删除、插入数据,在任何时候,都能保持任意节点左右子树都比较平衡的二叉查找树,这就是下一节要详细讲的,一种特殊的查找树,2-3查找树。2-3查找树的高度为 logn,所以插入、删除、查找操作的时间复杂度也比较稳定,是 O(logn)。
总的来说,二叉查找树的实现并不难,且当树的构造和随机模型近似时在各种实际场景中都能进行快速的查找和插入。另外,还支持高效的rank()、select()、delete()以及范围查找等操作。
以上是关于二叉查找树---红黑树基础的主要内容,如果未能解决你的问题,请参考以下文章