搜索树————AVL树和红黑树入门之作

1. 搜索树

1. 特点

  • 搜索树不一定是二叉树

二叉搜索树:对于任意一个节点,左边的值一定比这个节点的值小,右边的值一定比这个节点的值大;

特点:中序遍历是有序的;

2. 二叉搜索树OJ题

1. 寻找指定节点(力扣700)

给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode searchBST(TreeNode root, int val) {
        TreeNode cur = root;
        if(cur != null) {
            if(cur.val == val) {
                return cur;
            }else if(cur.val < val) {
                cur = cur.right;
            }else {
                cur = cur.left;
            }
            return searchBST(cur, val);
        }
        return null;
    }
}

注意:一定要在末尾才判断是否为空,如果你在开头就判空返回null的话就会错误;

2. 二叉搜索树的插入

在二叉搜索树中插入指定节点,注意二叉搜索树的插入规则,一定是插入在树的最下端哟;

1. 迭代版
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
        TreeNode cur = root;
        TreeNode parent = null;
        while(cur != null) {
            parent = cur;
            if(cur.val == val) {
                return root;
            }else if(val < cur.val) {
                cur = cur.left;
            }else {
                cur = cur.right;
            }
        }
        
        TreeNode newNode = new TreeNode(val);
        if(val > parent.val) {
            parent.right = newNode;
        }else {
            parent.left = newNode;
        }
        return root;
        
    }
}
2. 递归版(⭐)
    public TreeNode insertIntoBST(TreeNode root, int val) {
        if(root == null){
            return new TreeNode(val);
        }
        if(root.val < val){
            root.right = insertIntoBST(root.right,val);
        } else {
            root.left = insertIntoBST(root.left,val);
        }
        return root;
    }
3. 二叉搜索树的删除(力扣450)(迭代版)

删除的前提是要先查找到,所以框架是在查找的基础上实现的;

定义要删除的节点是cur;(下面的parent代表cur的父节点

  • 如果cur.left == null
    • parent.left 或者 parent.right = cur.right;
  • 如果cur.right == null
    • parent.left 或者 parent.right = cur.left;
  • 如果cur的左右子树都不为空(cur.left!=null && cur.right !=null
    • 用替换法:
      • 比cur的值大的值中最小的
      • 比cur的值小的值中最大的;
    • 以上两种方法任选一种即可,下面的代码选的第一种;
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode deleteNode(TreeNode root, int key) {
                TreeNode cur = root;
        //创建这个节点的目的是防止要删除的第一个节点就是root,没有这个节点的话那种情况下的parent为空,      //会报空指针异常
        TreeNode parentRes = new TreeNode(-1);
        TreeNode parent = parentRes;
        parent.right = root;
        while (cur != null) {
            if(key == cur.val) {
                //要删除的是cur,parent是cur的双亲节点;
                if(cur.right == null) {
                    //判断cur是parent的左子树还是右子树
                    if(cur == parent.left) {
                        parent.left = cur.left;
                    }else {
                        parent.right = cur.left;
                    }
                }else if(cur.left == null) {
                    if(cur == parent.left) {
                        parent.left = cur.right;
                    }else {
                        parent.right = cur.right;
                    }
                }else if(cur.left!=null && cur.right!=null) {
                    TreeNode replacedParent = cur;
                    TreeNode replaced = cur.right;
                    while(replaced.left != null) {
                        replacedParent = replaced;
                        replaced = replaced.left;
                    }
                    cur.val = replaced.val;
                    //注意这里一定要分这两种情况,因为有可能没有执行上面的while循环,此时replaced是					//replacedParent的右子树,而不是左子树,其他所有情况都是左子树;
                    if(replaced == replacedParent.left) {
                        replacedParent.left = replaced.right;
                    }else {
                        replacedParent.right = replaced.right;
                    }

                }else {
                    //左右子树都为空,即要删除的节点为叶子节点
                    if(cur == parent.left) {
                        parent.left = null;
                    }else {
                        parent.right = null;
                    }
                }
                return parentRes.right;

            }else if(key < cur.val) {
                //将parent = cur这一句放在这两个代码块中才能保证当上面的if块成立的时候parent是cur的父节点
                parent = cur;
                cur = cur.left;
            }else {
                parent = cur;
                cur = cur.right;
            }
        }
        return root;
    }
}

2. 平衡树概述(也属于搜索树)

搜索树的时间复杂度和树的高度有关,但是树的高度是在变化的;

为了解决树的高度会退化成O(n)的问题,引入了平衡树:

  • 二叉平衡搜索树
    • AVL树(不常用)
    • 红黑树 (常用)
  • 多叉平衡搜索树
    • B-树
    • B+树
    • B*树

以后提到搜索树,就代表已经是平衡的;


3. AVL树

1. 特点

  • 首先它是一颗搜索树,即左孩子大于根节点,右孩子小于根节点;
  • 树的每个节点的左右子树高度差绝对值不能超过一;

AVL树中保存平衡因子(balance factor)

  • 平衡因子:左子树高度 - 右子树高度;
    • 所以在AVL树中,每个节点的平衡因子只有三种情况:
      • 0:左右子树一样高;
      • -1:左子树比右子树高度小一;
      • 1:左子树比右子树高度大一;

2. AVL树的插入

这里不在叙述AVL树的查找,他和普通搜索树的查找是一样的;

但是插入和删除就跟上面的普通搜索树不一样了,因为插入和删除有可能导致树不再平衡;

下面来展开探究:

1. 右右失衡(左旋)

如下图插入元素99后导致右右失衡

  • 右右失衡:以失衡点为基准到插入的那个元素(图中有标记)
    在这里插入图片描述
    右右失衡的解决方法:

  • 做左旋:失衡点左旋

    • 具体细节:左旋后60那个节点直接就放在50的右孩子上;

上面左旋后的结果如下:
在这里插入图片描述
可以看到,左旋后就是平衡树了;

2. 右左失衡(右旋)

在这里插入图片描述

步骤:

  • (1):先对70做右旋(70上面的不变)
  • (2):再对50做左旋;

变换结果如下:
在这里插入图片描述

3. AVL树失衡情况总结

AVL树失衡情况只有如下四种:

  • 左左失衡
    • 对失衡节点做右旋;
  • 左右失衡
    • 对失衡节点左孩子做左旋,再对失衡节点做右旋;
  • 右右失衡
    • 对失衡节点做左旋;
  • 右左失衡
    • 对失衡节点右孩子做右旋,再对失衡节点做左旋;

(下面两种与上面两种对称记忆

4. 旋转的代码实现(⭐?)

要对一个节点进行左旋,上面已经了解了它的具体过程,这里我们直接翻译成代码即可:

    class Node {
        int key;
        int value;
        Node left;
        Node right;
        Node parent;   //做旋转的话必须需要parent,不然没法做;
    }
//左旋方法
    public static void rotateLeft(Node node) {
        Node right = node.right;    //node的右节点(不可能为空?)
        Node parent = node.parent;   //node的父节点(可能为空)
        Node leftOfRight = right.left;   //node的右节点的左节点(可能为空)
        //开始旋转----调整孩子部分
        right.left = node;
        node.right = leftOfRight;
        if(parent != null) {
            if(node == parent.left) {
                parent.left = right;
            }else {
                parent.right = right;
            }
        }
        //调整父节点部分
        right.parent = parent;
        node.parent = right;
        if(leftOfRight != null) {  //注意这里要判空
            leftOfRight.parent = node;
        }
    }

4. 红黑树

1. 特点:

  • 节点只能是红色或黑色
  • 根是黑色的
  • 叶子节点(null)是黑色的
  • 红色不能和红色相邻
  • 从跟到任意叶子路径上,黑色的数量一样多

以上特征保证了:
最长路径(黑红黑红黑红黑…)不会超过最短路径(黑黑黑黑…)的两倍, 这保证了树的高度差;

红黑树插入规则:

红黑树插入的节点一定是红色的

  • 根一定是黑色 (⭐)
  • 红色和红色不能相邻

2. 具体插入细则

在我举的例子中:(C:current P:parent G:grandparent U:uncle)
c代表破坏规则的红色节点,p是c的双亲,g是p的双亲,u是c的叔叔(即p的兄弟,可能不存在)

第一种: (U存在且是红色)

  • 即叔叔存在且红色
    • 这种好办,直接将P和U变黑,G变红就行
      • 务必注意G变红了可能影响上面的,如果破坏了上面的,则以G当成C(当前插入的节点),再来做对应变换;

在这里插入图片描述

第二种: (U不存在或者存在是黑色的)

  • ① P是G的左,C是P的左
    • 解决:G右旋,P变黑,G变红;
      在这里插入图片描述
  • ② P是G的右,C是P的右
    • 解决:G左旋,P变黑,G变红;

第三种: (U不存在或者存在是黑色的)

  • P是G的左,但C是P的右
    • 解决:P左旋,把P当成C,C当成P,成了上面的第二种中①的情况;
      在这里插入图片描述
  • P是G的右,但C是P的左
    • 解决:对P右旋,把P当成C,C当成P,成了上面的第二种中②的情况;

3. AVL树和红黑树的区别

  • AVL树的搜索效率稍高
  • 红黑树插入时调整频率更低

实际情况还得看实践,以上只是理论;

5. 二叉搜索树(AVL/红黑)的作用(⭐?)

上面讲述了二叉搜索树和红黑树的原理结构,那么在实际应用中,这些树有什么作用呢?

  • 二叉搜索树只用于内存搜索

为什么只用于内存搜索呢?能不能用于磁盘(硬盘的一种,硬盘包含机械、固态等)搜索呢?

因为二叉搜索树是链式的,所以存储不用顺序,一般在存储中都是乱序的,如下图:
在这里插入图片描述

假如上图的蓝色是内存条,当我们要获取6时,先读取内存得到根节点8,然后再次读取内存获取到5,然后再次读取内存获取到节点6;
设想如果蓝色是机械硬盘,当我们每获取一个节点,就得发起一次读磁盘,磁盘的IO速度是非常慢的,所以二叉搜索树只用于内存搜索;
这也是数据库的引擎的底层结构不是红黑树(数据库数据都是存在硬盘上的);
(当数据稍多,试想树高得多高(二叉嘛),树一高,读取磁盘次数可能就变多,效率就下降)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值