Skip to content

数据结构和算法之旅(八)- 二叉搜索树

约 1636 字大约 5 分钟

数据结构tree二叉搜索树

2017-03-15

更高效的快速查找: 二叉搜索树

如果想实现快速查找,就得引入新的算法或者数据结构了。最早了解的二分查找算法查找效率不错,是logn的。但是排序也是成本比较高的,先排序再查找有些得不偿失。 那么有什么新的算法或数据结构呢,答案是有的,即二叉搜索树。

定义

二叉搜索树有俩个特点:
1.树节点增加key属性,用来比较谁大谁小,key不可重复。
2.对于任意一个树节点,它的key比左子树的key都大,同时也比右子树的key都小。

性能

查找的性能是对数级别的,但是有些情况比如树不平衡的时候,时间复杂度又回到了O(n)。

code

以下是几个提前准备的算法基础。

前任的算法

后任的算法(与前任相反)

删除的算法(稍微复杂)

package bst;  
  
/**  
 * Binary Search Tree二叉搜索树  
 */  
public class BSTTree<K extends Comparable<K>, V> {  
  
    BSTNode<K, V> root;//根节点  
  
  
    static class BSTNode<K, V> {  
        K key;  
        V value;  
        BSTNode<K, V> left;  
        BSTNode<K, V> right;  
  
        public BSTNode(K key) {  
            this.key = key;  
        }  
  
        public BSTNode(K key, V value) {  
            this.key = key;  
            this.value = value;  
        }  
  
        public BSTNode(K key, V value, BSTNode<K, V> left, BSTNode<K, V> right) {  
            this.key = key;  
            this.value = value;  
            this.left = left;  
            this.right = right;  
        }  
    }  
  
    /**  
     * 查找关键字对应的值  
     */  
    public V get(K key) {  
        //对外隐藏了BSTNode的参数,不用暴露给外界  
        return doGet(root, key);  
    }  
  
    /**  
     * 递归方式实现get  
     */    private V doGet(BSTNode<K, V> p, K key) {  
        if (p == null) {  
            return null;//没有节点了,没找到,结束递归  
        }  
        int result = key.compareTo(p.key);  
        if (result < 0) {  
            return doGet(p.left, key);//如果待查找的key小于node,向左找  
        } else if (result > 0) {  
            return doGet(p.right, key);//向右找  
        } else {  
            return p.value;//找到了就返回value  
        }  
    }  
  
    /**  
     * 非递归实现get  
     * 尾递归的代码转换成非递归的实现非常简单,这样性能更好一些  
     * 比较java不支持尾递归自动优化,不妨都转换成循环的方式。  
     */  
    public Object _get(K key) {  
        BSTNode<K, V> p = root;  
        while (p != null) {  
            int result = key.compareTo(p.key);  
            /*  
            compareTo            -1 key < node.key            0 key == node.key            1 key > node.key             */            if (result < 0) {  
                p = p.left;  
            } else if (result > 0) {  
                p = p.right;  
            } else {  
                return p.value;  
            }  
        }  
        return null;  
    }  
  
    /**  
     * 查找最小关键字的值  
     */  
    public V min() {  
        return doMin(root);  
    }  
  
    /**  
     * 递归实现min  
     */    public V doMin(BSTNode<K, V> node) {  
        if (node == null) return null;  
        if (node.left == null) {//最小节点  
            return node.value;  
        }  
        return doMin(node.left);  
    }  
  
    /**  
     * 非递归实现min  
     */    public Object _doMin1(BSTNode node) {  
        if (node == null) return null;  
        while (node.left != null) {  
            node = node.left;  
        }  
        return node.value;  
    }  
  
    /**  
     * 查找最大关键字对应的值  
     * 最最小值类似,换找right即可,此处就省略了  
     */  
    public V max() {  
        return _doMax(root);  
    }  
  
    /**  
     * 非递归实现max  
     */    public V _doMax(BSTNode<K, V> node) {  
        if (node == null) return null;  
        while (node.right != null) {  
            node = node.right;  
        }  
        return node.value;  
    }  
  
  
    /**  
     * 存储关键字和对应值  
     * 先比较,再判断是更新还是新增  
     */  
    public void put(K key, V value) {  
        //1.key存在,更新  
        //2.key不存在,新增  
  
        //get的实现拿过来  
        BSTNode<K, V> p = root;  
        BSTNode<K, V> parent = null;  
  
        while (p != null) {  
            parent = p;  
            int result = key.compareTo(p.key);  
            if (result < 0) {  
                p = p.left;  
            } else if (result > 0) {  
                p = p.right;  
            } else {  
                //找到了,更新  
                p.value = value;  
                return;  
            }  
        }  
        //没找到,新增  
        //父节点就是parent,因为if、else if变成null之后parent就是目标父节点  
        if (parent == null) {  
            root = new BSTNode<>(key, value);  
        }  
        int compareTo = key.compareTo(parent.key);  
        if (compareTo < 0) {  
            parent.left = new BSTNode<>(key, value);  
        } else if (compareTo > 0) {  
            parent.right = new BSTNode<>(key, value);  
        }  
    }  
  
    /**  
     * 查找关键字的前驱值(前任,比它小的里面最大的)  
     * 对二叉搜索树做一次中序遍历一下即可得到升序的结果,但这样不高效。  
     * <p>  
     * 而是要通过一下总结下来的规律得出的。  
     * 情况1:节点有左子树,此时前任就是左子树的最大值  
     * 情况2:节点没有左子树,若离它最近的、自左而来的祖先就是前任  
     */  
    public V predecessor(K key) {  
        BSTNode<K, V> p = root;  
        BSTNode<K, V> fromLeft = null;  
  
        while (p != null) {  
            int result = key.compareTo(p.key);  
            if (result < 0) {  
                p = p.left;//向左走,祖先自右而来  
            } else if (result > 0) {  
                p = p.right;//向右走,祖先自左而来  
                fromLeft = p;  
            } else {  
                break;  
            }  
        }  
        //没找到节点  
        if (p == null) {  
            return null;  
        }  
        //情况1  
        if (p.left != null) {  
            return _doMax(p.left);//左子树最大值  
        }  
        //情况2  
        return fromLeft != null ? fromLeft.value : null;  
    }  
  
    /**  
     * 查找关键字的后驱值(后任,比它大的里面最小的)  
     */  
    public V successor(K key) {  
        BSTNode<K, V> p = root;  
        BSTNode<K, V> fromRight = null;  
  
        while (p != null) {  
            int result = key.compareTo(p.key);  
            if (result < 0) {  
                p = p.left;//向左走,祖先自右而来  
                fromRight = p;  
            } else if (result > 0) {  
                p = p.right;//向右走,祖先自左而来  
            } else {  
                break;  
            }  
        }  
        //没找到节点  
        if (p == null) {  
            return null;  
        }  
        //情况1 节点有右子树,此时后任就是右子树的最小值  
        if (p.right != null) {  
            return doMin(p.right);//右子树最小值  
        }  
        //情况2 节点数没有右子树,若离它最近的、自右而来的祖先就是后任  
        return fromRight != null ? fromRight.value : null;  
    }  
  
    /**  
     * 根据关键字删除  
     * 核心概念:把被删除的节点的后继节点顶上去  
     */  
    public V delete(K key) {  
        BSTNode<K, V> p = root;  
        BSTNode<K, V> parent = null;  
        while (p != null) {  
            int result = key.compareTo(p.key);  
            if (result < 0) {  
                parent = p;  
                p = p.left;//向左走,祖先自右而来  
            } else if (result > 0) {  
                parent = p;  
                p = p.right;//向右走,祖先自左而来  
            } else {  
                break;  
            }  
        }  
        if (p == null) return null;  
        //删除操作  
        if (p.left == null) {  
            //删除情况1(含情况3,走1,2都可以)  
            shift(parent, p, p.right);  
        } else if (p.right == null) {  
            //删除情况2  
            shift(parent, p, p.left);  
        } else {  
            //删除情况4  
            //4.1被删除节点找后继  
            BSTNode<K, V> s = p.right;  
            BSTNode<K, V> sPrent = p;//后继父亲  
            while (s.left != null) {  
                sPrent = s;  
                s = s.left;  
            }//s就是后继节点  
            //4.2删除节点与后继节点不相邻处理后继的后事  
            if (sPrent != p) {//不相邻  
                shift(sPrent, s, s.right);//不可能有左孩子  
                s.right = p.right;//顶上去的右  
            }  
            //4.3后继取代被删除节点  
            shift(parent, p, s);  
            s.left = p.left;//顶上去的左  
        }  
        return p.value;  
    }  
  
    /**  
     * 托孤方法 shift  
     *     * @param parent  被删除节点的父节点  
     * @param deleted 被删除到节点  
     * @param child   被顶上去的节点  
     */  
    private void shift(BSTNode<K, V> parent, BSTNode<K, V> deleted, BSTNode<K, V> child) {  
        if (parent == null) {  
            root = child;  
        } else if (deleted == parent.left) {  
            parent.left = child;  
        } else {  
            parent.right = child;  
        }  
    }  
}