深入理解二叉搜索树:定义、操作及平衡二叉树

引言

二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树结构,每个节点的左子树节点值小于根节点值,而右子树节点值大于根节点值。二叉搜索树在计算机科学中有着广泛的应用,尤其在动态查找表和优先队列的实现中。本文将详细介绍二叉搜索树的定义、基本操作及平衡二叉树(AVL树和红黑树)。

二叉搜索树的定义和操作

定义

二叉搜索树是一种节点值满足特定排序的二叉树,定义如下:

  • 每个节点都有一个值。
  • 左子树中所有节点的值都小于根节点的值。
  • 右子树中所有节点的值都大于根节点的值。
  • 左右子树也分别是二叉搜索树。

基本操作

插入操作

插入操作是向二叉搜索树中添加一个新节点。根据新节点的值与当前节点的值比较,决定插入左子树或右子树。以下是插入操作的代码示例:

java 复制代码
class TreeNode {
    int value;
    TreeNode left, right;

    // 构造函数
    TreeNode(int item) {
        value = item;
        left = right = null;
    }
}

class BinarySearchTree {
    TreeNode root;

    BinarySearchTree() {
        root = null;
    }

    // 插入值到二叉搜索树
    void insert(int value) {
        root = insertRec(root, value);
    }

    // 递归方式插入值
    TreeNode insertRec(TreeNode root, int value) {
        // 如果树为空,则创建新节点
        if (root == null) {
            root = new TreeNode(value);
            return root;
        }

        // 如果值小于根节点,则插入左子树
        if (value < root.value)
            root.left = insertRec(root.left, value);
        // 如果值大于根节点,则插入右子树
        else if (value > root.value)
            root.right = insertRec(root.right, value);

        // 返回(未变更的)节点指针
        return root;
    }

    // 中序遍历打印树
    void inorder() {
        inorderRec(root);
    }

    // 递归方式中序遍历树
    void inorderRec(TreeNode root) {
        if (root != null) {
            inorderRec(root.left);
            System.out.print(root.value + " ");
            inorderRec(root.right);
        }
    }

    // 测试插入操作
    public static void main(String[] args) {
        BinarySearchTree tree = new BinarySearchTree();
        tree.insert(50);
        tree.insert(30);
        tree.insert(20);
        tree.insert(40);
        tree.insert(70);
        tree.insert(60);
        tree.insert(80);

        // 打印插入后的树
        System.out.print("中序遍历结果: ");
        tree.inorder();  // 输出:20 30 40 50 60 70 80 
    }
}
插入操作图解
  1. 初始化一个空树。
  2. 插入第一个值 50,成为根节点。
  3. 插入值 30,比 50 小,插入到 50 的左子树。
  4. 插入值 20,比 30 小,插入到 30 的左子树。
  5. 插入值 40,比 30 大,插入到 30 的右子树。
  6. 插入值 70,比 50 大,插入到 50 的右子树。
  7. 插入值 60,比 70 小,插入到 70 的左子树。
  8. 插入值 80,比 70 大,插入到 70 的右子树。
plaintext 复制代码
    50
   /  \
  30  70
 / \  / \
20 40 60 80

20 30 40 50 60 70 80

删除操作

删除操作是从二叉搜索树中移除一个节点。根据要删除节点的位置及其子节点情况进行相应处理。以下是删除操作的代码示例:

java 复制代码
class BinarySearchTree {
    TreeNode root;

    BinarySearchTree() {
        root = null;
    }

    // 删除指定值的节点
    void deleteKey(int value) {
        root = deleteRec(root, value);
    }

    // 递归方式删除值
    TreeNode deleteRec(TreeNode root, int value) {
        // 基本情况:树为空
        if (root == null) return root;

        // 递归查找要删除的节点
        if (value < root.value)
            root.left = deleteRec(root.left, value);
        else if (value > root.value)
            root.right = deleteRec(root.right, value);
        else {
            // 节点只有一个子节点或没有子节点
            if (root.left == null)
                return root.right;
            else if (root.right == null)
                return root.left;

            // 节点有两个子节点,获取右子树中最小值
            root.value = minValue(root.right);

            // 递归删除右子树中的最小值节点
            root.right = deleteRec(root.right, root.value);
        }

        return root;
    }

    // 查找最小值
    int minValue(TreeNode root) {
        int minValue = root.value;
        while (root.left != null) {
            minValue = root.left.value;
            root = root.left;
        }
        return minValue;
    }

    // 中序遍历打印树
    void inorder() {
        inorderRec(root);
    }

    // 递归方式中序遍历树
    void inorderRec(TreeNode root) {
        if (root != null) {
            inorderRec(root.left);
            System.out.print(root.value + " ");
            inorderRec(root.right);
        }
    }

    // 测试删除操作
    public static void main(String[] args) {
        BinarySearchTree tree = new BinarySearchTree();
        tree.insert(50);
        tree.insert(30);
        tree.insert(20);
        tree.insert(40);
        tree.insert(70);
        tree.insert(60);
        tree.insert(80);

        System.out.println("删除前的树:");
        tree.inorder();  // 输出:20 30 40 50 60 70 80 

        tree.deleteKey(20);
        System.out.println("\n删除20后的树:");
        tree.inorder();  // 输出:30 40 50 60 70 80 

        tree.deleteKey(30);
        System.out.println("\n删除30后的树:");
        tree.inorder();  // 输出:40 50 60 70 80 

        tree.deleteKey(50);
        System.out.println("\n删除50后的树:");
        tree.inorder();  // 输出:40 60 70 80 
    }
}
删除操作图解

假设我们要从上面的树中删除值 503020

  1. 删除 20:节点 20 没有子节点,直接删除。
  2. 删除 30:节点 30 有一个子节点 40,用 40 替换 30
  3. 删除 50:节点 50 有两个子节点,用其右子树的最小值 60 替换 50,然后删除 60
plaintext 复制代码
删除前:
    50
   /  \
  30  70
 / \  / \
20 40 60 80

删除 20 后:
    50
   /  \
  30  70
   \  / \
   40 60 80

删除 30 后:
    50
   /  \
  40  70
     / \
    60 80

删除 50 后:
    60
   /  \
  40  70
       \
       80

删除前 50 30 70 20 40 60 80 删除20后 50 30 70 40 60 80 删除30后 50 40 70 60 80 删除50后 60 40 70 80

查找操作

查找操作是搜索二叉搜索树中是否存在某个特定值。根据目标值与当前节点值比较,决定在左子树或右子树中继续查找。以下是查找操作的代码示例:

java 复制代码
class BinarySearchTree {
    TreeNode root;

    BinarySearchTree() {
        root = null;
    }

    // 查找指定值是否存在
    boolean search(int value) {
        return searchRec(root, value);
    }

    // 递归方式查找值
    boolean searchRec(TreeNode root, int value) {
        // 基本情况:树为空或值为根节点值
        if (root == null || root.value == value) return root != null;

        // 值小于根节点值,则在左子树中查找
        if (value < root.value)
            return searchRec(root.left, value);
        // 值大于根节点值,则在右子树中查找
        return searchRec(root.right, value);
    }

    // 插入值到二叉查找树
    void insert(int value) {
        root = insertRec(root, value);
    }

    // 递归方式插入值
    TreeNode insertRec(TreeNode root, int value) {
        // 如果树为空,则创建新节点
        if (root == null) {
            root = new TreeNode(value);
            return root;
        }

        // 如果值小于根节点,则插入左子树
        if (value < root.value)
            root.left = insertRec(root.left, value);
        // 如果值大于根节点,则插入右子树
        else if (value > root.value)
            root.right = insertRec(root.right, value);

        // 返回(未变更的)节点指针
        return root;
    }

    // 测试查找操作
    public static void main(String[] args) {
        BinarySearchTree tree = new BinarySearchTree();
        tree.insert(50);
        tree.insert(30);
        tree.insert(20);
        tree.insert(40);
        tree.insert(70);
        tree.insert(60);
        tree.insert(80);

        // 测试查找操作
        System.out.println(tree.search(50));  // 输出:true
        System.out.println(tree.search(90));  // 输出:false
    }
}
查找操作图解

假设我们在上面的树中查找值 5090

  1. 查找 50:从根节点开始,50 正是根节点值,查找成功。
  2. 查找 90:从根节点 50 开始,90 大于 50,查找右子树。然后 90 大于 70,查找右子树。接着 90 大于 80,发现右子树为空,查找失败。

50 30 70 20 40 60 80 查找50 查找90 null

平衡二叉树

平衡二叉树是一类特殊的二叉搜索树,通过自动调整树的结构,使树的高度保持在较低水平,从而提高查找、插入和删除操作的效率。常见的平衡二叉树有 AVL 树和红黑树。

AVL 树

AVL 树是一种自平衡二叉搜索树,它的每个节点都需要满足以下平衡条件:任意节点的左右子树高度差不超过 1。AVL 树通过旋转操作来保持平衡。

AVL 树的旋转操作
  1. 右旋(Right Rotation)
  2. 左旋(Left Rotation)
  3. 左右旋(Left-Right Rotation)
  4. 右左旋(Right-Left Rotation)

右旋 左旋 左右旋 右左旋

红黑树

红黑树是一种自平衡二叉搜索树,每个节点都有颜色属性(红色或黑色),并且需要满足以下性质:

  1. 节点是红色或黑色。
  2. 根节点是黑色。
  3. 每个叶节点(NIL 节点)是黑色。
  4. 如果一个节点是红色,则它的两个子节点都是黑色。
  5. 从一个节点到该节点的所有后代叶节点的所有路径上,包含相同数目的黑色节点。
红黑树的旋转和颜色翻转
  1. 左旋(Left Rotation)
  2. 右旋(Right Rotation)
  3. 颜色翻转(Color Flip)

左旋 右旋 颜色翻转

红黑树的插入操作

红黑树的插入操作和普通二叉搜索树相同,但插入新节点后需要进行重新平衡操作。以下是红黑树的插入操作代码示例:

java 复制代码
class RedBlackTreeNode {
    int value;
    RedBlackTreeNode left, right, parent;
    boolean color; // true for Red, false for Black

    // 构造函数
    RedBlackTreeNode(int item) {
        value = item;
        left = right = parent = null;
        color = true; // new nodes are red by default
    }
}

class RedBlackTree {
    private RedBlackTreeNode root;
    private final RedBlackTreeNode TNULL;

    // 初始化
    public RedBlackTree() {
        TNULL = new RedBlackTreeNode(0);
        TNULL.color = false;
        TNULL.left = null;
        TNULL.right = null;
        root = TNULL;
    }

    // 前序遍历
    private void preOrderHelper(RedBlackTreeNode node) {
        if (node != TNULL) {
            System.out.print(node.value + " ");
            preOrderHelper(node.left);
            preOrderHelper(node.right);
        }
    }

    // 中序遍历
    private void inOrderHelper(RedBlackTreeNode node) {
        if (node != TNULL) {
            inOrderHelper(node.left);
            System.out.print(node.value + " ");
            inOrderHelper(node.right);
        }
    }

    // 后序遍历
    private void postOrderHelper(RedBlackTreeNode node) {
        if (node != TNULL) {
            postOrderHelper(node.left);
            postOrderHelper(node.right);
            System.out.print(node.value + " ");
        }
    }

    // 查找树中的节点
    private RedBlackTreeNode searchTreeHelper(RedBlackTreeNode node, int key) {
        if (node == TNULL || key == node.value) {
            return node;
        }
        if (key < node.value) {
            return searchTreeHelper(node.left, key);
        }
        return searchTreeHelper(node.right, key);
    }

    // 平衡插入操作
    private void fixInsert(RedBlackTreeNode k) {
        RedBlackTreeNode u;
        while (k.parent.color == true) {
            if (k.parent == k.parent.parent.right) {
                u = k.parent.parent.left;
                if (u.color == true) {
                    u.color = false;
                    k.parent.color = false;
                    k.parent.parent.color = true;
                    k = k.parent.parent;
                } else {
                    if (k == k.parent.left) {
                        k = k.parent;
                        rightRotate(k);
                    }
                    k.parent.color = false;
                    k.parent.parent.color = true;
                    leftRotate(k.parent.parent);
                }
            } else {
                u = k.parent.parent.right;
                if (u.color == true) {
                    u.color = false;
                    k.parent.color = false;
                    k.parent.parent.color = true;
                    k = k.parent.parent;
                } else {
                    if (k == k.parent.right) {
                        k = k.parent;
                        leftRotate(k);
                    }
                    k.parent.color = false;
                    k.parent.parent.color = true;
                    rightRotate(k.parent.parent);
                }
            }
            if (k == root) {
                break;
            }
        }
        root.color = false;
    }

    // 左旋
    private void leftRotate(RedBlackTreeNode x) {
        RedBlackTreeNode y = x.right;
       

 x.right = y.left;
        if (y.left != TNULL) {
            y.left.parent = x;
        }
        y.parent = x.parent;
        if (x.parent == null) {
            this.root = y;
        } else if (x == x.parent.left) {
            x.parent.left = y;
        } else {
            x.parent.right = y;
        }
        y.left = x;
        x.parent = y;
    }

    // 右旋
    private void rightRotate(RedBlackTreeNode x) {
        RedBlackTreeNode y = x.left;
        x.left = y.right;
        if (y.right != TNULL) {
            y.right.parent = x;
        }
        y.parent = x.parent;
        if (x.parent == null) {
            this.root = y;
        } else if (x == x.parent.right) {
            x.parent.right = y;
        } else {
            x.parent.left = y;
        }
        y.right = x;
        x.parent = y;
    }

    // 插入节点
    public void insert(int key) {
        RedBlackTreeNode node = new RedBlackTreeNode(key);
        node.parent = null;
        node.value = key;
        node.left = TNULL;
        node.right = TNULL;
        node.color = true;

        RedBlackTreeNode y = null;
        RedBlackTreeNode x = this.root;

        while (x != TNULL) {
            y = x;
            if (node.value < x.value) {
                x = x.left;
            } else {
                x = x.right;
            }
        }

        node.parent = y;
        if (y == null) {
            root = node;
        } else if (node.value < y.value) {
            y.left = node;
        } else {
            y.right = node;
        }

        if (node.parent == null) {
            node.color = false;
            return;
        }

        if (node.parent.parent == null) {
            return;
        }

        fixInsert(node);
    }

    // 查找树中的节点
    public RedBlackTreeNode searchTree(int k) {
        return searchTreeHelper(this.root, k);
    }

    // 前序遍历
    public void preorder() {
        preOrderHelper(this.root);
    }

    // 中序遍历
    public void inorder() {
        inOrderHelper(this.root);
    }

    // 后序遍历
    public void postorder() {
        postOrderHelper(this.root);
    }

    // 打印树
    private void printHelper(RedBlackTreeNode root, String indent, boolean last) {
        if (root != TNULL) {
            System.out.print(indent);
            if (last) {
                System.out.print("R----");
                indent += "   ";
            } else {
                System.out.print("L----");
                indent += "|  ";
            }
            String sColor = root.color ? "RED" : "BLACK";
            System.out.println(root.value + "(" + sColor + ")");
            printHelper(root.left, indent, false);
            printHelper(root.right, indent, true);
        }
    }

    public void printTree() {
        printHelper(this.root, "", true);
    }

    // 测试红黑树
    public static void main(String[] args) {
        RedBlackTree bst = new RedBlackTree();
        bst.insert(55);
        bst.insert(40);
        bst.insert(65);
        bst.insert(60);
        bst.insert(75);
        bst.insert(57);

        bst.printTree();

        System.out.println("\n前序遍历:");
        bst.preorder();
        System.out.println("\n\n中序遍历:");
        bst.inorder();
        System.out.println("\n\n后序遍历:");
        bst.postorder();
    }
}
红黑树插入操作图解

假设我们在一棵空的红黑树中插入以下节点:55、40、65、60、75、57,以下是每一步的插入和相应的颜色标注和旋转操作:

  1. 插入 55,成为根节点,颜色变为黑色。
  2. 插入 40,为 55 的左子节点,颜色为红色。
  3. 插入 65,为 55 的右子节点,颜色为红色。
  4. 插入 60,为 65 的左子节点,颜色为红色,需要左旋 65
  5. 插入 75,为 65 的右子节点,颜色为红色。
  6. 插入 57,为 60 的左子节点,颜色为红色,需要右旋 60,然后左旋 55

40 红 55 黑 57 红 60 红 65 红 75 红

结论

通过上述讲解和实例代码,我们详细展示了二叉搜索树的定义、基本操作及平衡二叉树(AVL 树和红黑树)。二叉搜索树在计算机科学中有着广泛的应用,平衡二叉树通过保持树的高度在较低水平,从而提高查找、插入和删除操作的效率。希望这篇博客对您有所帮助!记得关注、点赞和收藏哦,以便随时查阅更多优质内容!


如果您觉得这篇文章对您有帮助,请关注我的CSDN博客,点赞并收藏这篇文章,您的支持是我持续创作的动力!


关键内容总结

  • 二叉搜索树的定义和基本操作
  • 二叉搜索树的插入、删除和查找操作
  • 平衡二叉树:AVL 树和红黑树
  • AVL 树的旋转操作
  • 红黑树的性质和操作

推荐阅读:深入探索设计模式专栏 ,详细讲解各种设计模式的应用和优化。点击查看:深入探索设计模式


特别推荐:设计模式实战专栏 ,深入解析设计模式的实际应用,提升您的编程技巧。点击查看:设计模式实战

如有任何疑问或建议,欢迎在评论区留言讨论。谢谢阅读!

相关推荐
shinelord明1 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
新手小袁_J7 分钟前
JDK11下载安装和配置超详细过程
java·spring cloud·jdk·maven·mybatis·jdk11
呆呆小雅7 分钟前
C#关键字volatile
java·redis·c#
დ旧言~8 分钟前
专题八:背包问题
算法·leetcode·动态规划·推荐算法
Monly218 分钟前
Java(若依):修改Tomcat的版本
java·开发语言·tomcat
boligongzhu9 分钟前
DALSA工业相机SDK二次开发(图像采集及保存)C#版
开发语言·c#·dalsa
Eric.Lee20219 分钟前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频
Ttang2310 分钟前
Tomcat原理(6)——tomcat完整实现
java·tomcat
7yewh12 分钟前
嵌入式Linux QT+OpenCV基于人脸识别的考勤系统 项目
linux·开发语言·arm开发·驱动开发·qt·opencv·嵌入式linux
钱多多_qdd21 分钟前
spring cache源码解析(四)——从@EnableCaching开始来阅读源码
java·spring boot·spring