数据结构之二叉搜索树(BST)



前言

前段时间整理了一篇关于MYSQL索引的文章,其中谈到了B-Tree和B+Tree,加上这两天coding涉及到了一些树结构的处理。恰逢数据结构已经忘干净了,所以准备重新捡起来看一下,先从二叉搜索树开始吧,后续会陆续更新。


1、二叉树

先整理一下二叉树的基本性质:
性质1:二叉树的第i层上至多有 2 i − 1 2^{i-1} 2i1(i≥1)个节点:

  1. 当i=1时,第1层的节点个数为 2 0 2^0 20 = 1,成立
  2. 当i>1时,第i层的节点个数为 2 i − 1 2^{i-1} 2i1,那么第i+1层的节点个数就是 2 i 2^i 2i
  3. 因为二叉树节点最大的度是2,那么第i+1层的节点节点个数最多是第i层的两倍,于是就有2 ∗ \ast 2 i − 1 2^{i-1} 2i1= 2 i 2^i 2i

性质2:深度为h的二叉树中至多含有 2 h − 1 2^h-1 2h1个节点

  1. 深度为h的二叉树节点个数最多为 2 0 2^0 20 + 2 1 2^1 21 + 2 2 2^2 22 + … + 2 h − 1 2^{h-1} 2h1 = 2 h − 1 2^h-1 2h1

性质3:若在任意一棵二叉树中,有 n 0 n_{0} n0个叶子节点,有 n 2 n_{2} n2个度为2的节点,则必有n0=n2+1
设0度节点个数为 n 0 n_0 n0,1度节点个数为 n 1 n_1 n1,2度节点个数为 n 2 n_2 n2,树节点总个数为n,那么就有n = n 0 n_0 n0 + n 1 n_1 n1 + n 2 n_2 n2
0度节点没有孩子,1度节点有一个孩子,2度节点有2个孩子,只有root节点不是任何节点的孩子,则n = n 1 n_1 n1 + n 2 n_2 n2 ∗ 2 \ast2 2+1 ②
由①②得: n 0 n_0 n0 = n 2 n_2 n2 + 1
性质4:具有n个节点的满二叉树深为log2n+1
由性质2得,深度为h的二叉树中至多含有 2 h − 1 2^h-1 2h1个节点 ,所以具有n个节点的二叉树深度至少为 log ⁡ 2 ( n + 1 ) \log_2(n+1) log2(n+1)

二叉树:
![](https://img-blog.csdnimg.cn/ff991ecdce0146a5bc682492ccfaf325.png

2、二叉搜索树

二叉搜索树,见名知其意:也是一个二叉树,与一般二叉树不同的是,对于树中任意一个节点,其左子树的所有节点值都小于该节点的值,右子树的所有节点都大于该节点的值,且任意左右子树也是二叉搜索树。二叉树的中序遍历是有序的,是一个递增序列,相对来说,对查询会变得更“友好”一些。

以下是一个二叉搜索树:
在这里插入图片描述
通过图示可以看出,任意节点的左子树的任意节点值都小于该值,反之亦然。
树整体结构是从左到右依次递增,左永远小于右,根节点在中间。所以根据写出中序(左根右)遍历得出结果:2 3 4 5 6 9 12 14 15 16 17 。
要认真理解这个遍历顺序,能够更容易帮助理解其中的一些规律。

3、二叉搜索树实现

先定义树节点,其中包括节点值key、左子树、右子树以及父节点,如下:

public class BinarySearchNode<T extends Comparable<T>> {
        T key;                
        BinarySearchNode<T> left;    
        BinarySearchNode<T> right;   
        BinarySearchNode<T> parent;    

        public BinarySearchNode(T key, BinarySearchNode<T> parent, BinarySearchNode<T> left, BinarySearchNode<T> right) {
            this.key = key;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }

        public T getKey() {
            return key;
        }

        public String toString() {
            return "key:"+key;
        }
    }

二叉搜索树具体实现,包括树的前中后序遍历,节点查找,插入节点,删除节点,节点的前驱、后继等操作,

package com.binarysearch;


public class BSTree<T extends Comparable<T>> {

    private BinarySearchNode<T> root;    //root
 
    public BSTree() {
        root=null;
    }

    /**
     * 前序遍历
     * @param tree
     */
    private void previous(BinarySearchNode<T> tree) {
        if(tree != null) {
            System.out.print(tree.key+" ");
            previous(tree.left);
            previous(tree.right);
        }
    }

    public void previous() {
        previous(root);
    }


    /**
     * 中序遍历
     * @param tree
     */
    public void middle(BinarySearchNode<T> tree) {
        if(tree != null) {
            middle(tree.left);
            System.out.print(tree.key+" ");
            middle(tree.right);
        }
    }

    public void middle() {
        middle(root);
    }


    /**
     * 后序遍历
     * @param tree
     */
    public void after(BinarySearchNode<T> tree) {
        if(tree != null)
        {
            after(tree.left);
            after(tree.right);
            System.out.print(tree.key+" ");
        }
    }

    public void after() {
        after(root);
    }


    /**
     * 查找target节点
     * @param root
     * @param target
     * @return
     */
    public BinarySearchNode<T> search(BinarySearchNode<T> root, T target) {
        if (root == null){
            return root;
        }

        //比较对象大小
        int cmp = target.compareTo(root.key);

        if (cmp < 0){
            return search(root.left, target);
        }
        else if (cmp > 0){
            return search(root.right, target);
        }else {
            return root;
        }

    }

    public BinarySearchNode<T> search(T key) {
        return search(root, key);
    }

    /*
     * (非递归实现)查找"二叉树x"中键值为key的节点
     */
    private BinarySearchNode<T> iterativeSearch(BinarySearchNode<T> x, T key) {
        while (x!=null) {
            int cmp = key.compareTo(x.key);

            if (cmp < 0)
                x = x.left;
            else if (cmp > 0)
                x = x.right;
            else
                return x;
        }

        return x;
    }

    public BinarySearchNode<T> iterativeSearch(T key) {
        return iterativeSearch(root, key);
    }


    /**
     * 返回最小节点,最小节点一定在"最左边"
     * @param tree
     * @return
     */
    private BinarySearchNode<T> getMin(BinarySearchNode<T> tree) {
        if (tree == null)
            return null;

        while(tree.left != null)
            tree = tree.left;

        return tree;
    }

    public T getMin() {
        BinarySearchNode<T> p = getMin(root);
        if (p != null)
            return p.key;

        return null;
    }


    /**
     * 查找最大节点 ,最大节点一定在"最右边"
     * @param tree
     * @return
     */
    private BinarySearchNode<T> getMax(BinarySearchNode<T> tree) {
        if (tree == null)
            return null;

        while(tree.right != null)
            tree = tree.right;
        return tree;
    }

    public T getMax() {
        BinarySearchNode<T> p = getMax(root);
        if (p != null)
            return p.key;

        return null;
    }


    /**
     * 查找节点的前驱节点
     * 前驱节点: 小于该节点的最大节点
     * 查找前驱和后继节点和树的中序遍历,前驱其实获取的就是该节点在中序遍历序列中的前一个节点
     */
    public BinarySearchNode<T> precursor(BinarySearchNode<T> node) {
        // 分两种情况讨论
        // 1.如果该节点有左子树,那前驱节点就是左子树的最大节点,它应该位于左子树的"最右节点"
        if (node.left != null)
            return getMax(node.left);

        // 如果没有左子树,需要判断该节点和直接父节点p之间的关系
        // 1.如果该节点是父节点的左孩子,那前驱节点就是父节点,
        // 2.如果该节点是父节点的右孩子,则沿着父节点一直向上查找,直到找到一个父节点p,p需要满足(假设p的父节点为q):是其父节点q的右孩子,那么q就是要找的前驱节点
        //   这不难理解,中序遍历,遍历完父节点就会遍历右子树,从右子树递归先找最左节点,那么父节点就是最左节点的前驱。
        BinarySearchNode<T> y = node.parent;
        while ((y != null) && (node == y.left)) {
            node = y;
            y = y.parent;
        }

        return y;
    }


    /**
     * 查找指定节点的后继节点
     * 后继节点:大于该节点的最小节点,
     *
     */
    public BinarySearchNode<T> successor(BinarySearchNode<T> node) {
        // 与前驱原理类似
        //1.如果该节点有右子树,前驱节点就是右子树上的最小节点,它应该位于右子树的"最左边"
        if (node.right != null)
            return getMin(node.right);

        // 如果没有右子树,就判断该节点和直接父节点p之间的关系
        // 1.如果该节点是左孩子,那么他的后继节点就是它的父节点
        // 2.如果该节点是右孩子,则继续沿着父节点向上查找,直到找到一个父节点p,p需满足:是其父节点q的左孩子,那么q就是要找的后继节点
        //   或者这么理解,因为该节点是右孩子,根据中序遍历的规则,遍历完该节点的树,就应该向上遍历父级树,如果找到一个父节点是一个左孩子,那么该父节点的父节点一定是第一个大于它的值。
        BinarySearchNode<T> y = node.parent;
        while ((y != null) && (node == y.right)) {
            node = y;
            y = y.parent;
        }

        return y;
    }


    private void insert(BSTree<T> bst, BinarySearchNode<T> z) {
        int cmp;
        BinarySearchNode<T> y = null;
        BinarySearchNode<T> x = bst.root;

        // 先找到要插入节点的位置
        while (x != null) {
            y = x;
            cmp = z.key.compareTo(x.key);
            if (cmp < 0)
                x = x.left;
            else
                x = x.right;
        }

        z.parent = y;
        //没有根节点 插入节点就是根节点
        if (y == null)
            bst.root = z;
        else {
            //判断节点放在左边还是右边
            cmp = z.key.compareTo(y.key);
            if (cmp < 0)
                y.left = z;
            else
                y.right = z;
        }
    }


    /**
     * 节点插入
     */
    public void insert(T key) {
        BinarySearchNode<T> z=new BinarySearchNode<T>(key,null,null,null);

        // 如果新建结点失败,则返回。
        if (z != null)
            insert(this, z);
    }


    //获取根节点
    private BinarySearchNode<T> getRootNode(BSTree<T> bst){
        return bst.root;
    }


    /**
     * 删除节点
     * @param root
     * @param z
     * @return
     */
    private BinarySearchNode<T> remove(BinarySearchNode<T> root, BinarySearchNode<T> z){
        if (root == null) return root;
        //递归找到要删除的节点
        int cmp = z.key.compareTo(root.key);
        if (cmp < 0){
            root.left = remove(root.left,z);
        }else if (cmp > 0){
            root.right = remove(root.right,z);
        }else {
            //如果该节点没有右子树,就用该节点的左子树代替该节点
            if (root.right == null){
                return root.left;
            }
            //如果该节点没有左子树,就用该节点的右子树代替该节点
            if (root.left == null){
                return root.right;
            }
            //如果既有左子树,也有右子树,那就把节点的左子树作为右子树的"最左子树"的左子树
            //找到右子树的最左树
            BinarySearchNode<T> r = root.right;
            while (r.left != null){
                r = r.left;
            }
            r.left = root.left;
            root = root.left;

        }
        return root;
    }


    public void remove(T key) {
        BinarySearchNode<T> z, node;

        if ((z = search(root, key)) != null)
            if ( (node = remove(getRootNode(this), z)) != null)
                node = null;
    }

    /**
     * 销毁
     * @param tree
     */
    private void destroy(BinarySearchNode<T> tree) {
        if (tree==null)
            return ;
		//递归删除即可
        if (tree.left != null)
            destroy(tree.left);
        if (tree.right != null)
            destroy(tree.right);

        tree=null;
    }

    public void clear() {
        destroy(root);
        root = null;
    }


}

总结

以上就是一个二叉搜索树的基本原理和代码实现,相对二叉树就是多了一个排序功能,总体相对简单,后续时间充裕的话陆续会更新一些其他的数据结构知识,能力一般,如果有什么问题,还请各位看官及时纠正。
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值