二叉搜索树(Binary Search Tree)

关于二叉树中的点到底用“结点”还是“结点”。结点被认为是一个有处理能力的实体,比如网络上的一台计算机;而结点则只是一个交叉点,像“结绳记事”,打个结做个标记,仅此而已。还有就是,要记住:一般算法中点的都是结点

二叉搜索树又叫二叉排序树、二叉查找树,支持多种操作,包括Search、Minimum、Maximum、Predecessor、Successor、Insert和Delete等操作,且其基本操作所花费的时间与这棵树的高度成正比。

完全二叉树的最坏运行时间为: O ( l o g   n ) O({\bf log}\ n) O(log n),树如果由 n n n个结点的组成的线性链,则最好运行时间为: O ( n ) O(n) O(n)

二叉搜索树的定义

二叉搜索树又称为做二叉排序树、二叉查找树。其要么是一课空树,要么是一个满足以下性质的二叉树:

  1. 若它的左子树不空,则左子树上所有结点的关键字均小于根结点关键字
  2. 若它的右子树不空,则右子树上所有结点的关键字均大于根结点关键字
  3. 它的左右子树依旧是二叉搜索树
  4. 没右关键字相等的结点

算法导论中指出,左右子树均可以包含和根结点大小相等的元素

二叉搜索树具有的特点:

  1. 按中序遍历二叉搜索树所得的中序序列是一个递增的有序序列。
  2. 统一个数据集合可构造的二叉搜索树不唯一,但中序序列相同。

Search

二叉搜索树在搜索某个关键字遇到结点x时,将关键字kx.key比较:

  1. 如果k==x.key,查找终止并返回x
  2. 如果k<x.key,继续在x的左子树中查找,如果左子树为空则返回null
  3. 如果k>x.key,继续在x的右子树中查找,如果左子树为空则返回null

上述过程所需的运行时间为 O ( h ) O(h) O(h),其中 h h h表示树的高度。其代码为:

public TreeNode search(TreeNode node, int key) {
    while (node != null) {
        if (node.val == key) return node;
        else if (node.val > key) node = node.left;
        else node = node.right;
    }
    return node;
}

Minimum

二叉搜索树从根结点沿着左孩子的指针直到遇到null,并返回左孩子指针为null的结点。

上述过程所需的运行时间为 O ( h ) O(h) O(h),其中 h h h表示树的高度。其代码为:

public TreeNode minimum(TreeNode node) {
    while (node.left != null) node = node.left;
    return node;
}

Maximum

二叉搜索树从根结点沿着右孩子的指针直到遇到null,并返回右孩子指针为null的结点。

上述过程所需的运行时间为 O ( h ) O(h) O(h),其中 h h h表示树的高度。其代码为:

public TreeNode maximum(TreeNode node) {
    while (node.right != null) node = node.right;
    return node;
}

Successor

二叉搜索树寻找后继分为两种情况:

  1. 结点x的右子树非空,那么x的后继就是x右子树的最左结点(右子树中最小的结点)。
  2. 结点x的右子树为空,那么简单地从x沿着树向上搜索,直到搜索到当前根结点是父结点的左子树的根为止,此时该父结点是x的后继。

上述过程所需的运行时间为 O ( h ) O(h) O(h),其中 h h h表示树的高度。其代码为:

public TreeNode successor(TreeNode node) {
    if (node.right != null) return minimum(node.right);
    while (node.p != null && node == node.p.right) node = node.p;
    return node.p;
}

Predecessor

二叉搜索树寻找前驱分为两种情况:

  1. 结点x的左子树非空,那么x的前驱就是x左子树的最右结点(左子树中最大的结点)。
  2. 结点x的右子树为空,那么简单地从x沿着树向上搜索,直到搜索到当前根结点是父结点的右子树的根为止,此时该父结点是x的前驱

上述过程所需的运行时间为 O ( h ) O(h) O(h),其中 h h h表示树的高度。其代码为:

public TreeNode predecessor(TreeNode node) {
    if (node.left != null) return maximum(node.left);
    while (node.p != null && node == node.p.left) node = node.p;
    return node.p;
}

Insert

插入结点之后仍要满足二叉搜索树的特性。如果要插入的值为z,二叉搜索树在结点x处的处理情况分为两种:

  1. 如果当前结点为空,直接将z值插入;
  2. 如果当前结点不空:
    1. 如果z<x.key,继续在x的左子树中插入
    2. 如果z>x.key,继续在x的右子树中插入

其代码为:

public TreeNode insert(TreeNode root, int z) {
    if (root == null) {
        root = new TreeNode(z);
        return root;
    }
    TreeNode node = root;
    while (true) {
        if (z < node.val) {
            if (node.left == null) {
                node.left = new TreeNode(z);
                node.left.p = node;
                break;
            }
            node = node.left;
        }
        if (z > node.val) {
            if (node.right == null) {
                node.right = new TreeNode(z);
                node.right.p = node;
                break;
            }
            node = node.right;
        }
    }
    return root;
}

Delete

删除结点之后仍要满足二叉搜索树的特性。在二叉搜索树T中删除一个结点z分为三种情况:

  1. 如果结点z没有孩子结点,则直接删除该结点
  2. 如果结点z只有左子树或者右子树中的一个,则直接继承:将该子树移到被删除结点的位置
  3. 如果结点z拥有两个子树,则用后继或者先驱结点取代被删除的结点。

其代码为:

public TreeNode delete(TreeNode node, int x) {
    TreeNode root = node;
    while (node != null) {
        if (node.val == x) {
            if (node.left != null && node.right != null) { // 情况 3
                TreeNode maximum = maximum(node.left);
                node.val = maximum.val;
                if (maximum.p.left != null && maximum.p.left.val == maximum.val) maximum.p.left = maximum.left;
                if (maximum.p.right != null && maximum.p.right.val == maximum.val) maximum.p.right = null;
            } else if (node.left == null && node.right == null) { // 情况 1
                if (node.p.left != null && node.p.left.val == x) node.p.left = null;
                if (node.p.right != null && node.p.right.val == x) node.p.right = null;
            } else { // 情况 2
                if (node.left != null) {
                    node.val = node.left.val;
                    node.right = node.left.right;
                    if (node.left.left != null) node.left.left.p = node;
                    if (node.left.right != null) node.left.right.p = node;
                    node.left = node.left.left;
                } else if (node.right != null) {
                    node.val = node.right.val;
                    node.left = node.right.left;
                    if (node.right.left != null) node.right.left.p = node;
                    if (node.right.right != null) node.right.right.p = node;
                    node.right = node.right.right;
                }
            }
            break;
        }
        if (node.val > x && node.left != null) node = node.left;
        if (node.val < x && node.right != null) node = node.right;
    }
    return root;
}

算法导论第三版中给出了构建深度接近 O ( l o g   n ) O({\bf log}\ n) O(log n)的随机构建二叉搜索树,按照随机的次序插入关键字到一颗空树中。为降低树的高度还出现了各种“平衡"搜索树。

完整代码:

package org.example;

public class Template {
    public TreeNode search(TreeNode node, int key) {
        while (node != null) {
            if (node.val == key) return node;
            else if (node.val > key) node = node.left;
            else node = node.right;
        }
        return node;
    }

    public TreeNode minimum(TreeNode node) {
        while (node.left != null) node = node.left;
        return node;
    }

    public TreeNode maximum(TreeNode node) {
        while (node.right != null) node = node.right;
        return node;
    }

    public TreeNode successor(TreeNode node) {
        if (node.right != null) return minimum(node.right);
        while (node.p != null && node == node.p.right) node = node.p;
        return node.p;
    }

    public TreeNode predecessor(TreeNode node) {
        if (node.left != null) return maximum(node.left);
        while (node.p != null && node == node.p.left) node = node.p;
        return node.p;
    }

    public TreeNode insert(TreeNode root, int z) {
        if (root == null) {
            root = new TreeNode(z);
            return root;
        }
        TreeNode node = root;
        while (true) {
            if (z < node.val) {
                if (node.left == null) {
                    node.left = new TreeNode(z);
                    node.left.p = node;
                    break;
                }
                node = node.left;
            }
            if (z > node.val) {
                if (node.right == null) {
                    node.right = new TreeNode(z);
                    node.right.p = node;
                    break;
                }
                node = node.right;
            }
        }
        return root;
    }

    public TreeNode delete(TreeNode node, int x) {
        TreeNode root = node;
        while (node != null) {
            if (node.val == x) {
                if (node.left != null && node.right != null) {
                    TreeNode maximum = maximum(node.left);
                    node.val = maximum.val;
                    if (maximum.p.left != null && maximum.p.left.val == maximum.val) maximum.p.left = maximum.left;
                    if (maximum.p.right != null && maximum.p.right.val == maximum.val) maximum.p.right = null;
                } else if (node.left == null && node.right == null) {
                    if (node.p.left != null && node.p.left.val == x) node.p.left = null;
                    if (node.p.right != null && node.p.right.val == x) node.p.right = null;
                } else {
                    if (node.left != null) {
                        node.val = node.left.val;
                        node.right = node.left.right;
                        if (node.left.left != null) node.left.left.p = node;
                        if (node.left.right != null) node.left.right.p = node;
                        node.left = node.left.left;
                    } else if (node.right != null) {
                        node.val = node.right.val;
                        node.left = node.right.left;
                        if (node.right.left != null) node.right.left.p = node;
                        if (node.right.right != null) node.right.right.p = node;
                        node.right = node.right.right;
                    }
                }
                break;
            }
            if (node.val > x && node.left != null) node = node.left;
            if (node.val < x && node.right != null) node = node.right;
        }
        return root;
    }

    public static void main(String[] args) {
        Template template = new Template();

        TreeNode left = new Template().new TreeNode(3);
        TreeNode right = new Template().new TreeNode(6);
        TreeNode root = new Template().new TreeNode(4, left, right);
        left.p = root;
        right.p = root;

        System.out.println(template.search(root, 5));
        System.out.println(template.minimum(root).val);
        System.out.println(template.maximum(root).val);
        TreeNode successor = template.successor(root);
        System.out.println(successor == null ? null : successor.val);
        TreeNode predecessor = template.predecessor(right);
        System.out.println(predecessor == null ? null : predecessor.val);

        TreeNode node = template.insert(root, 1);
        System.out.println(node.left.left.p.val);

        TreeNode node2 = template.insert(root, 0);
        TreeNode node3 = template.insert(root, 2);
        TreeNode node1 = template.delete(root, 1);
        System.out.println(node1.left.left.right.val);

    }

    class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode p;

        TreeNode() {
        }

        TreeNode(int val) {
            this.val = val;
        }

        TreeNode(int val, TreeNode left, TreeNode right) {
            this.val = val;
            this.left = left;
            this.right = right;
        }

    }
}

题目链接

  1. 450. 删除二叉搜索树中的结点

参考文献:

  1. 二叉搜索树
  2. 二叉搜索树及其操作详解
  3. Thomas,H.Cormen,Charles,E.Leiserson,Ronald,L.Rivest,Clifford,Stein,殷建平,徐云,王刚,刘晓光,苏明,邹恒明,王宏志. 算法导论(原书第3版)[J]. 计算机教育, 2013(10):1.
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值