数据结构之二叉排序树

心已入冬 提交于 2020-03-18 14:31:18

某厂面试归来,发现自己落伍了!>>>

概念:

二叉搜索树是一种节点值之间具有一定数量级次序的二叉树。对于树中的每个节点:

  1. 若其左子树存在,则其左子树中每个节点的值都不大于该节点的值。
  2. 若其右子树存在,则其右子树中每个节点的值都不小于该节点的值。
  3. 该节点的左右子树都是二叉搜索树。
  4. 没有键值相等的节点。

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

    private BSTNode<T> mRoot;

    @Data
    public class BSTNode<T extends Comparable<T>> {
        T key;
        BSTNode<T> left;
        BSTNode<T> right;
        BSTNode<T> parent;

        public BSTNode() {
            super();
        }

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

前驱与后继:

前驱:该节点左子树中最大的节点。数值紧挨着小于的值。

public BSTNode<T> predecessor(BSTNode<T> x) {
        // 如果x存在左孩子,则"x的前驱结点"为 "以其左孩子为根的子树的最大结点"。
        if (x.left != null)
            return maximum(x.left);

        // 如果x没有左孩子。则x有以下两种可能:
        // (01) x是"一个右孩子",则"x的前驱结点"为 "它的父结点"。
        // (01) x是"一个左孩子",则查找"x的最低的父结点,并且该父结点要具有右孩子",找到的这个"最低的父结点"就是"x的前驱结点"。
        BSTNode<T> y = x.parent;
        while ((y != null) && (x == y.left)) {
            x = y;
            y = y.parent;
        }

        return y;
    }

后继:该节点右子树中最小的节点。数值紧挨着大于的值。

public BSTNode<T> successor(BSTNode<T> x) {
        // 如果x存在右孩子,则"x的后继结点"为 "以其右孩子为根的子树的最小结点"。
        if (x.right != null)
            return minimum(x.right);

        // 如果x没有右孩子。则x有以下两种可能:
        // (01) x是"一个左孩子",则"x的后继结点"为 "它的父结点"。
        // (02) x是"一个右孩子",则查找"x的最低的父结点,并且该父结点要具有左孩子",找到的这个"最低的父结点"就是"x的后继结点"。
        BSTNode<T> y = x.parent;
        while ((y != null) && (x == y.right)) {
            x = y;
            y = y.parent;
        }

        return y;
    }

查找时间复杂度:

O(log2(n))~O(n)

  1. 针对第一种极限情况:完全二叉树中树的深度与节点的个数为n=2^(d+1)-1。设二叉树中的节点格式为nc。那么nc<=2^(d+1)-1。那么就有,d+1>=log2(nc+1),因为d+1为查找次数,完全二叉树中查找次数为(log2(nc+1))。
  2. 针对第二种极限情况:查找过程类似于数组的遍历,所以查找次数为O(n)。

递归查找:

private BSTNode<T> search(BSTNode<T> x, T key) {
        if (x == null)
            return x;

        int cmp = key.compareTo(x.key);
        if (cmp < 0)
            return search(x.left, key);
        else if (cmp > 0)
            return search(x.right, key);
        else
            return x;
    }
public BSTNode<T> search(T key) {
        return search(mRoot, key);
    }

非递归查找:

private BSTNode<T> iterativeSearch(BSTNode<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 BSTNode<T> iterativeSearch(T key) {
   return iterativeSearch(mRoot, key);
}

构造过程:

二叉树的构造过程与二叉树的搜索过程大致相同。

不同的地方为:

  1. 查询的过程为,比较元素值是否相等,相等则返回,不相等则判断大小情况,迭代查询左右子树,直到找到相等的元素,或子节点为空,返回节点不存在。
  2. 插入的过程为,比较元素值是否相等,相等则返回表示已存在,不相等则判断大小情况,迭代查询左右子树,直到找到相等的元素,或子节点为空,则将节点插入该空节点位置。
private void insert(BSTree<T> bst, BSTNode<T> z) {
        int cmp;
        BSTNode<T> y = null;
        BSTNode<T> x = bst.mRoot;

        // 查找z的插入位置
        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.mRoot = z;
        else {
            cmp = z.key.compareTo(y.key);
            if (cmp < 0)
                y.left = z;
            else
                y.right = z;
        }
    }
public void insert(T key) {
        BSTNode<T> z = new BSTNode<T>(key, null, null, null);

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

删除过程:

二叉搜索树的删除具有三种情况:

  1. 删除度为0的节点。
  2. 删除度为1的节点。
  3. 删除度为2的节点。

删除度为0的节点:

度为0的节点,这种节点没有左右子树,是叶子节点直接删除即可。

删除度为1的节点:

度为1的节点,这种节点只有左子树或者右子树的一个,这种节点删除后,为了满足二叉搜索树的结构特性,需要将其的左子树或者右子树上移。

删除度为2的节点:

度为2的节点,这种节点同时具备左子树和右子树,删除后,为了维持二叉搜索树的结构特性,需要在其的左子树中查找出来一个最大值来填充删除的节点。

实际操作为:

  1. 查找出左子树中的最大值节点Nm。
  2. 替换待删除节点的值N为Nm。
  3. 删除Nm节点。

因为Nm作为左子树的最大值节点,必定是度为0或者1的节点,这样就转化为上面的情况。

个人认为这里也可以用右子树的最小值。

private BSTNode<T> remove(BSTree<T> bst, BSTNode<T> z) {
        BSTNode<T> x = null;
        BSTNode<T> y = null;

        if ((z.left == null) || (z.right == null))
            y = z;
        else
            y = successor(z);

        if (y.left != null)
            x = y.left;
        else
            x = y.right;

        if (x != null)
            x.parent = y.parent;

        if (y.parent == null)
            bst.mRoot = x;
        else if (y == y.parent.left)
            y.parent.left = x;
        else
            y.parent.right = x;

        if (y != z)
            z.key = y.key;

        return y;
    }
public void remove(T key) {
    BSTNode<T> z, node;

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

操作:

查找最大的节点:

private BSTNode<T> maximum(BSTNode<T> tree) {
        if (tree == null)
            return null;

        while (tree.right != null)
            tree = tree.right;
        return tree;
    }
public T maximum() {
        BSTNode<T> p = maximum(mRoot);
        if (p != null)
            return p.key;

        return null;
    }

查找最小的节点:

private BSTNode<T> minimum(BSTNode<T> tree) {
        if (tree == null)
            return null;

        while (tree.left != null)
            tree = tree.left;
        return tree;
    }
public T minimum() {
        BSTNode<T> p = minimum(mRoot);
        if (p != null)
            return p.key;

        return null;
    }

打印二叉搜索树:

private void print(BSTNode<T> tree, T key, int direction) {

        if (tree != null) {

            if (direction == 0)    // tree是根节点
                System.out.printf("%2d is root\n", tree.key);
            else                // tree是分支节点
                System.out.printf("%2d is %2d's %6s child\n", tree.key, key, direction == 1 ? "right" : "left");

            print(tree.left, tree.key, -1);
            print(tree.right, tree.key, 1);
        }
    }
public void print() {
        if (mRoot != null)
            print(mRoot, mRoot.key, 0);
    }

销毁二叉搜索树:

private void destroy(BSTNode<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(mRoot);
        mRoot = null;
    }

总结:

二叉搜索树的查询,构造和删除的性能与树的高度有关。相对于数组只存储数值,二叉树多了存储指针占用的空间,二叉树是一种以空间换时间的思路。如果能保持二叉树尽可能的平衡,避免向类似于数组的斜树发展,其时间复杂度会有显著降低。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!