二叉排序树的查找、插入、删除

我们知道,二分查找可以缩短查找的时间,但是有个要求就是 查找的数据必须是有序的。每次查找、操作时都要维护一个有序的数据集,于是有了二叉排序树这个概念。

二叉排序树的定义

什么是二叉排序树 Binary Sort Tree, BST
二叉排序树,又称二叉查找树、二叉搜索树。

二叉排序树是具有下列性质的二叉树:

1.若左子树不空,则左子树上所有结点的值均小于它的根结点的值;

2.若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;

3.左、右子树也分别为二叉排序树。
在这里插入图片描述

也就是说,二叉排序树中,左子树都比节点小,右子树都比节点大,递归定义。

根据二叉排序树这个特点我们可以知道,二叉排序树的中序遍历一定是从小到大的,比如上图,中序遍历结果是:

1 3 4 6 7 8 10 13 14

二叉排序树的关键操作

1.查找

根据二叉排序树的定义,我们可以知道在查找某个元素时:

先比较它与根节点,相等就返回;或者根节点为空,说明树为空,也返回;

如果它比根节点小,就从根的左子树里进行递归查找;

如果它比根节点大,就从根的右子树里进行递归查找。

可以看到,这就是一个 二分查找。

代码实现:

public class BinarySearchTree {
    private BinaryTreeNode mRoot;   //根节点

    public BinarySearchTree(BinaryTreeNode root) {
        mRoot = root;
    }

    /**
     * 在整个树中查找某个数据
     *
     * @param data
     * @return
     */
    public BinaryTreeNode search(int data) {
        return search(mRoot, data);
    }

    /**
     * 在指定二叉排序树中查找数据
     *
     * @param node
     * @param data
     * @return
     */
    public BinaryTreeNode search(BinaryTreeNode node, int data) {
        if (node == null || node.getData() == data) {    //节点为空或者相等,直接返回该节点
            return node;
        }
        if (data < node.getData()) {    //比节点小,就从左子树里递归查找
            return search(node.getLeftChild(), data);
        } else {        //否则从右子树
            return search(node.getRightChild(), data);
        }
    }
}

可以看到,在二叉排序树中查找是十分简单的,但是这依赖于每次插入、删除元素时对整个 排序树 结构的维护。

2.插入

二叉树中的插入,主要分两步:查找、插入:

先查找有没有整个元素,有的话就不用插入了,直接返回;
没有就插入到之前查到(对比)好的合适的位置。
插入时除了设置数据,还需要跟父节点绑定,让父节点意识到有你这个孩子:比父节点小的就是左孩子,大的就是右孩子。

代码实现:

/**
 * 插入到整个树中
 *
 * @param data
 */
public void insert(int data) {
    if (mRoot == null) {     //如果当前是空树,新建一个
        mRoot = new BinaryTreeNode();
        mRoot.setData(data);
        return;
    }

    searchAndInsert(null, mRoot, data);     //根节点的父亲为 null

}

/**
 * 两步走:查找、插入
 *
 * @param parent 要绑定的父节点
 * @param node   当前比较节点
 * @param data   数据
 */
private BinaryTreeNode searchAndInsert(BinaryTreeNode parent, BinaryTreeNode node, int data) {
    if (node == null) {  //当前比较节点为 空,说明之前没有这个数据,直接新建、插入
        node = new BinaryTreeNode();
        node.setData(data);
        if (parent != null) {    //父节点不为空,绑定关系
            if (data < parent.getData()) {
                parent.setLeftChild(node);
            } else {
                parent.setRightChild(node);
            }
        }
        return node;
    }
    //对比的节点不为空
    if (node.getData() == data) {    //已经有了,不用插入了
        return node;
    } else if (data < node.getData()) {   //比节点小,从左子树里查找、插入
        return searchAndInsert(node, node.getLeftChild(), data);
    } else {
        return searchAndInsert(node, node.getRightChild(), data);
    }
}
3.删除

插入操作和查找比较类似,而删除则相对复杂一点,需要根据删除节点的情况分类来对待:

1.如果要删除的节点正好是叶子节点,直接删除就 Ok 了;

2.如果要删除的节点还有子节点,就需要建立父节点和子节点的关系:

  • 如果只有左孩子或者右孩子,直接把这个孩子上移放到要删除的位置就好了;
  • 如果有两个孩子,就需要选一个合适的孩子节点作为新的根节点,该节点称为继承节点

新节点要求要比所有左子树大,比所有右子树小,怎么选择呢?

要比所有左子树的值大、右子树小,就从右子树里找最小的好了;
同样也可以从左子树里找最大的。

两种选择方法都可以,本文选用右子树里最小的节点,也就是右子树中最左边的节点。

代码实现:

/**
 * 在整个树中 查找指定数据节点的父亲节点
 *
 * @param data
 * @return
 */
public BinaryTreeNode searchParent(int data) {
    return searchParent(null, mRoot, data);
}

/**
 * 在指定节点下 查找指定数据节点的父亲节点
 *
 * @param parent 当前比较节点的父节点
 * @param node   当前比较的节点
 * @param data   查找的数据
 * @return
 */
public BinaryTreeNode searchParent(BinaryTreeNode parent, BinaryTreeNode node, int data) {
    if (node == null) { //比较的节点为空返回空
        return null;
    }
    if (node.getData() == data) {    //找到了目标节点,返回父节点
        return parent;
    } else if (data < node.getData()) {   //数据比当前节点小,左子树中递归查找
        return searchParent(node, node.getLeftChild(), data);
    } else {
        return searchParent(node, node.getRightChild(), data);
    }
}

/**
 * 删除指定数据的节点
 *
 * @param data
 */
public void delete(int data) {
    if (mRoot == null || mRoot.getData() == data) {  //根节点为空或者要删除的就是根节点,直接删掉
        mRoot = null;
        return;
    }
    //在删除之前需要找到它的父亲
    BinaryTreeNode parent = searchParent(data);
    if (parent == null) {        //如果父节点为空,说明这个树是空树,没法删
        return;
    }

    //接下来该找要删除的节点了
    BinaryTreeNode deleteNode = search(parent, data);
    if (deleteNode == null) {    //树中找不到要删除的节点
        return;
    }
    //删除节点有 4 种情况
    //1.左右子树都为空,说明是叶子节点,直接删除
    if (deleteNode.getLeftChild() == null && deleteNode.getRightChild() == null) {
        //删除节点
        deleteNode = null;
        //重置父节点的孩子状态,告诉他你以后没有这个儿子了
        if (parent.getLeftChild() != null && parent.getLeftChild().getData() == data) {
            parent.setLeftChild(null);
        } else {
            parent.setRightChild(null);
        }
        return;
    } else if (deleteNode.getLeftChild() != null && deleteNode.getRightChild() == null) {
        //2.要删除的节点只有左子树,左子树要继承位置
        if (parent.getLeftChild() != null && parent.getLeftChild().getData() == data) {
            parent.setLeftChild(deleteNode.getLeftChild());
        } else {
            parent.setRightChild(deleteNode.getLeftChild());
        }
        deleteNode = null;
        return;
    } else if (deleteNode.getRightChild() != null && deleteNode.getLeftChild() == null) {
        //3.要删除的节点只有右子树,右子树要继承位置
        if (parent.getLeftChild() != null && parent.getLeftChild().getData() == data) {
            parent.setLeftChild(deleteNode.getRightChild());
        } else {
            parent.setRightChild(deleteNode.getRightChild());
        }

        deleteNode = null;
    } else {
        //4.要删除的节点儿女双全,既有左子树又有右子树,需要选一个合适的节点继承,这里使用右子树中最左节点
        BinaryTreeNode copyOfDeleteNode = deleteNode;   //要删除节点的副本,指向继承节点的父节点
        BinaryTreeNode heresNode = deleteNode.getRightChild(); //要继承位置的节点,初始为要删除节点的右子树的树根
        //右子树没有左孩子了,他就是最小的,直接上位
        if (heresNode.getLeftChild() == null) {
            //上位后,兄弟变成了孩子
            heresNode.setLeftChild(deleteNode.getLeftChild());
        } else {
            //右子树有左孩子,循环找到最左的,即最小的
            while (heresNode.getLeftChild() != null) {
                copyOfDeleteNode = heresNode;       //copyOfDeleteNode 指向继承节点的父节点
                heresNode = heresNode.getLeftChild();
            }
            //找到了继承节点,继承节点的右子树(如果有的话)要上移一位
            copyOfDeleteNode.setLeftChild(heresNode.getRightChild());
            //继承节点先继承家业,把自己的左右孩子变成要删除节点的孩子
            heresNode.setLeftChild(deleteNode.getLeftChild());
            heresNode.setRightChild(deleteNode.getRightChild());
        }
        //最后就是确认位置,让要删除节点的父节点认识新儿子
        if (parent.getLeftChild() != null && parent.getLeftChild().getData() == data) {
            parent.setLeftChild(heresNode);
        } else {
            parent.setRightChild(heresNode);
        }
    }
}

补充带有根节点的删除操作:点击查看

运行代码测试

可以看到,二叉排序树的查找、添加较简单,删除逻辑比较多,我们以下图为例:

在这里插入图片描述

测试代码:这个要先序插入,否则不能构成上图的树

@Test
public void delete() throws Exception {
    //先序插入到二叉排序树中
    BinarySearchTree binarySearchTree = new BinarySearchTree(null);
    binarySearchTree.insert(8);
    binarySearchTree.insert(3);
    binarySearchTree.insert(1);
    binarySearchTree.insert(6);
    binarySearchTree.insert(4);
    binarySearchTree.insert(7);
    binarySearchTree.insert(10);
    binarySearchTree.insert(14);
    binarySearchTree.insert(13);
    

    //中序遍历
    binarySearchTree.iterateMediumOrder(binarySearchTree.getRoot());
    System.out.println("");
    //查找某个数据
    System.out.println(binarySearchTree.search(10).getData());
    //删除某个数据对应的元素
    binarySearchTree.delete(6);
    //中序遍历删除后的二叉排序树
    binarySearchTree.iterateMediumOrder(binarySearchTree.getRoot());
}

运行结果:

1 3 4 6 7 8 10 13 14 
10
1 3 4 7 8 10 13 14 

总结

二叉排序树的性能取决于二叉树的层数:

最好的情况是 O(logn),存在于完全二叉排序树情况下,其访问性能近似于折半查找;
最差时候会是 O(n),比如插入的元素是有序的,生成的二叉排序树就是一个链表,这种情况下,需要遍历全部元素才行(见下图 b)。
在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我来为您解答如何实现二叉排序树查找。 1. 定义二叉排序树查找算法函数 二叉排序树查找算法函数可以使用递归或迭代方式实现,以下是一个递归实现的示例代码: ```python def search_bst(root, val): if not root or root.val == val: return root elif root.val > val: return search_bst(root.left, val) else: return search_bst(root.right, val) ``` 2. 二叉排序树插入算法函数 二叉排序树插入算法函数也可以使用递归或迭代方式实现,以下是一个递归实现的示例代码: ```python def insert_bst(root, val): if not root: return TreeNode(val) elif root.val > val: root.left = insert_bst(root.left, val) else: root.right = insert_bst(root.right, val) return root ``` 3. 建立二叉排序树插入算法 建立二叉排序树插入算法可以使用一个循环来不断插入新的节点,以下是一个示例代码: ```python def build_bst(nums): if not nums: return None root = TreeNode(nums[0]) for i in range(1, len(nums)): insert_bst(root, nums[i]) return root ``` 4. 二叉排序树删除一个结点的操作实现 二叉排序树删除一个结点的操作需要考虑多种情况,包括删除的节点有无子节点、删除的节点只有一个子节点、删除的节点有两个子节点等,以下是一个示例代码: ```python def delete_bst(root, val): if not root: return None if root.val == val: if not root.left: return root.right if not root.right: return root.left if root.left and root.right: temp = root.right while temp.left: temp = temp.left root.val = temp.val root.right = delete_bst(root.right, temp.val) elif root.val > val: root.left = delete_bst(root.left, val) else: root.right = delete_bst(root.right, val) return root ``` 5. 打印二叉排序树 二叉排序树的打印可以使用中序遍历来实现,以下是一个示例代码: ```python def inorder_traversal(root): if not root: return [] res = [] stack = [] while root or stack: while root: stack.append(root) root = root.left root = stack.pop() res.append(root.val) root = root.right return res ``` 可以使用以上函数来打印二叉排序树的所有节点值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值