二叉搜索树

1. 二叉搜索树的概念

二叉搜索树,也称为二叉排序树或二叉查找树。一棵不为空的二叉搜索树满足以下性质:

  1. 非空左子树的所有键值小于其根结点的键值。
  2. 非空右子树的所有键值大于其根结点的键值。
  3. 左,右子树都是二叉搜索树。

2. 二叉搜索树的查找

从二叉搜索树BST中查找元素X,返回其所在结点的地址。二叉搜索树的查找过程可以描述为以下步骤:

  1. 查找从根结点开始,如果树为空,直接返回 null
  2. 若查找树非空,则根结点关键字与 X 进行比较,并进行不同的处理。
    1. x 小于根结点的键值,在左子树中搜索;
    2. x 大于根结点的键值,在右子树中搜索;
    3. 若两者比较的结果相等,搜索完成,直接返回指向结点的指针。

image-20200820101124146

我们用递归来实现查找过程,

public TreeNode<T> find(T x, TreeNode<T> bst) {
    if (bst == null) {
        System.out.println("树为空,查找失败!");
        return null; //查找失败
    }

    int result = x.compareTo(bst.data);
    if (result > 0) {
        // x 大于根结点的值,向右子树递归查找
        return find(x, bst.right);
    } else if (result < 0) {
        // x 小于根结点的值,向左子树递归查找
        return find(x, bst.left);
    } else {
        // 找到 x 的值,直接返回结点所在的指针
        return bst;
    }
}

在这个递归实现中,两个递归过程都是尾递归,可以改成用迭代函数来实现,提高执行效率。

public TreeNode<T> findNonRecursive(T x, TreeNode<T> bst) {
    while (bst != null) {
        int result = x.compareTo(bst.data);
        if (result > 0) {
            // 向右子树中移动,继续查找
            bst = bst.right;
        } else if (result < 0) {
            // 向左子树中移动,继续查找
            bst = bst.left;
        } else {
            // x == bst.data
            // 查找成功,返回结点的找到结点的地址
            return bst;
        }
    }
    // 查找失败
    return null;
}

除了通用的查找外,二叉搜索树经常要使用到最大和最小元素的查找。对于一棵二叉搜索树来说,最大元素一定是在树的最右分支的端结点上,最小元素一定是在树的最左分支的端结点上。

image-20200827114732562

最大元素和最小元素的递归和非递归实现为:

// 查找树的最小值
public TreeNode<T> findMin(TreeNode<T> bst) {
    if (bst == null) {
        // 空的二叉搜索树,返回NULL
        return null;
    } else if (bst.left == null) {
        // 找到最左叶结点并返回
        return bst;
    } else {
        // 沿左分支继续查找
        return findMin(bst.left);
    }
}

// 查找树的最大值
public TreeNode<T> findMax(TreeNode<T> bst) {
    if (bst == null) {
        // 空的二叉搜索树
        return null;
    } else if (bst.right == null) {
        // 找到最右的叶结点并返回
        return bst;
    } else {
        // 沿右分支继续查找
        return findMax(bst.right);
    }
}

// 最小值非递归实现
public TreeNode<T> findMinNonRecursive(TreeNode<T> bst) {
    if (bst != null) {
        while (bst.left != null) {
            // 沿左分支继续查找,直到最右叶结点
            bst = bst.left;
        }
    }

    return bst;
}

// 查找最大值非递归实现
public TreeNode<T> findMaxNonRecursive(TreeNode<T> bst) {
    if (bst != null) {
        while (bst.right != null) {
            // 沿右分支继续查找,直到最右叶结点
            bst = bst.right;
        }
    }

    return bst;
}

3. 二叉搜索树的插入

插入指定结点到一棵二叉树中,这个过程我们关键要找到这个结点要插入的位置,可以采用与查找类似的方法。

// 插入操作
public TreeNode<T> insert(T x, TreeNode<T> bst) {
    if (bst == null) {
        // 若原树为空,生成并返回一个结点的二叉搜索树
        bst = new TreeNode<>();
        bst.data = x;
        bst.left = bst.right = null;
    } else {
        // 开始查找要插入元素的位置
        int result = x.compareTo(bst.data);
        if (result < 0) {
            // 递归插入左子树
            bst.left = insert(x, bst.left);
        } else if (result > 0) {
            // 递归插入右子树
            bst.right = insert(x, bst.right);
        } /*else {
                // x 元素已经存在, 不做任何操作
                //
            }*/
    }

    return bst;
}

通过递归左右子树,找到要插入的位置,然后插入结点。

4. 二叉搜索树的删除

删除一棵二叉树,分为三种情况,分别来分析一下:

第一种,要删除的结点是树的叶子结点,那么可以直接删除,并修改其父结点的指针为 null,例如,删除下面这棵二叉搜索树 35 这个结点:

image-20200831180447446

第二种,要删除的结点有一个孩子结点,那么删除该结点后,要修改其父结点的指针到要删除结点的孩子结点上。例如,删除下面这棵二叉搜索树的33结点。

image-20200831180648814

第三种,要删除的结点有左、右两个子树,用另一结点替代被删除结点:右子树的最小元素或者左子树的最大元素。例如,删除41 这个结点的时候。

image-20200831180831521

实现如下:

public TreeNode<T> delete(T x, TreeNode<T> bst) {
    TreeNode<T> tmp;
    if (bst == null) {
        System.out.println("树为空,要删除的元素未找到!");
        return null;
    } else if (x.compareTo(bst.data) < 0) { //左子树递归删除
        bst.left = delete(x, bst.left);
    } else if (x.compareTo(bst.data) > 0) {
        bst.right = delete(x, bst.right); //右子树递归删除
    } else { //找到要删除的节点
        if (bst.left != null && bst.right != null) { //被删除结点有左右两个子结点
            tmp = findMin(bst.right);
            // 在右子树中找最小的元素填充删除结点
            bst.data = tmp.data;
            bst.right = delete(bst.data, bst.right);
        } else { // 被删除结点有一个或无子结点
            if (bst.left == null) {
                // 有右孩子或无子结点
                bst = bst.right;
            } else if (bst.right == null) {
                bst = bst.left;
            }
        }
    }

    return bst;
}

参考:浙江大学陈越老师的数据结构课程

posted @ 2020-09-04 18:32  chenxueqiang  阅读(543)  评论(0编辑  收藏  举报