23°

数据结构之二叉排序树

概念:

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

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

public class BSTree<T extends Comparable<T>> {
private BSTNode&lt;T&gt; mRoot;

@Data
public class BSTNode&lt;T extends Comparable&lt;T&gt;&gt; {
    T key;
    BSTNode&lt;T&gt; left;
    BSTNode&lt;T&gt; right;
    BSTNode&lt;T&gt; parent;

    public BSTNode() {
        super();
    }

    public BSTNode(T key, BSTNode&lt;T&gt; left, BSTNode&lt;T&gt; right, BSTNode&lt;T&gt; 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&lt;T&gt; y = x.parent;
    while ((y != null) &amp;&amp; (x == y.left)) {
        x = y;
        y = y.parent;
    }

    return y;
}</code></pre> 

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

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&lt;T&gt; y = x.parent;
    while ((y != null) &amp;&amp; (x == y.right)) {
        x = y;
        y = y.parent;
    }

    return y;
}</code></pre> 

查找时间复杂度:

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 &lt; 0)
        return search(x.left, key);
    else if (cmp &gt; 0)
        return search(x.right, key);
    else
        return x;
}</code></pre> 
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 &lt; 0)
            x = x.left;
        else if (cmp &gt; 0)
            x = x.right;
        else
            return x;
    }

    return x;
}</code></pre> 
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 &lt; 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 &lt; 0)
            y.left = z;
        else
            y.right = z;
    }
}</code></pre> 
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;
}</code></pre> 
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;
}</code></pre> 
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;
}</code></pre> 
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);
    }
}</code></pre> 
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;
}</code></pre> 
public void clear() {
        destroy(mRoot);
        mRoot = null;
    }

总结:

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

本文由【九分石人】发布于开源中国,原文链接:https://my.oschina.net/linqiankun/blog/3197451

全部评论: 0

    我有话说: