数据结构之二叉查找树

定义:二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树

如图就是一颗二叉查找树:

根节点为8,根节点的左子树都比8小,根节点的右子树都比8大。

如果将二叉查找树投影到一条直线上,保证一个节点的左子树出现在它的左边,右子树出现在它的右边,那么可以得到一条有序的数列。

例如图中的二叉查找树投影后即为:1、3、4、6、7、8、10、13、14

 

上面的有序序列也是中序遍历的结果,

 

基本实现:

我们定义每个节点都有一个键、一个值、一条左链接、一条右链接、和一个节点计数器。


import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Queue;

/**
 * @author yuan
 * @date 2019/2/26
 * @description 二叉查找树
 */
public class BST<Key extends Comparable, Value> {

    private Node root;

    private class Node{
        private Key key;
        private Value val;
        private Node left, right;
        /**
         * 以该节点为根的子树中的节点总数
         */
        private int n;

        public Node(Key key, Value val, int n) {
            this.key = key;
            this.val = val;
            this.n = n;
        }
    }

    public int size(){
        return size(root);
    }

    private int size(Node x) {
        if (x == null) {
            return 0;
        }
        return x.n;
    }

    public Value get(Key key) {
        return get(root, key);
    }

    private Value get(Node x, Key key) {
        if (key == null) {
            throw new IllegalArgumentException("calls get() with a null key");
        }
        if (x == null) {
            return null;
        }
        int cmp = key.compareTo(x.key);
        if (cmp < 0) {
            return get(x.left, key);
        } else if (cmp > 0) {
            return get(x.right, key);
        } else {
            return x.val;
        }
    }

    public void put(Key key, Value val) {
        if (key == null) {
            throw new IllegalArgumentException("calls put() with a null key");
        }
        if (val == null) {
            delete(key);
            return;
        }
        root = put(root, key, val);
    }

    private Node put(Node x, Key key, Value val) {
        if (x == null) {
            // 如果key不存在,新建节点
            return new Node(key, val, 1);
        }
        int cmd = key.compareTo(x.key);
        if (cmd < 0) {
            // 如果被查找的键小于根节点,在左子树继续插入该键
            x.left = put(x.left, key, val);
        } else if (cmd > 0) {
            // 如果被查找的键大于于根节点,在左子树继续插入该键
            x.right = put(x.right, key, val);
        } else {
            // 如果key存在,则更新
            x.val = val;
        }
        x.n = size(x.left) + size(x.right) + 1;
        return x;
    }

    public boolean isEmpty(){
        return size() == 0;
    }


    public Key min(){
        if (isEmpty()) {
            throw new NoSuchElementException("calls min() with empty symbol table");
        }
        return min(root).key;
    }

    private Node min(Node x) {
        if (x.left == null) {
            return x;
        }
        return min(x.left);
    }

    public Key max(){
        if (isEmpty()) {
            throw new NoSuchElementException("calls max() with empty symbol table");
        }
        return max(root).key;
    }

    private Node max(Node x) {
        if (x.right == null) {
            return x;
        }
        return max(x.right);
    }

    public void deleteMin(){
        if (isEmpty()) {
            throw new NoSuchElementException("Symbol table underflow");
        }
        root = deleteMin(root);

    }

    private Node deleteMin(Node x) {
        if (x.left == null) {
            return x.right;
        }
        x.left = deleteMin(x.left);
        x.n = size(x.left) + size(x.right) + 1;
        return x;
    }

    public void deleteMax(){
        if (isEmpty()) {
            throw new NoSuchElementException("Symbol table underflow");
        }
        root = deleteMax(root);
    }

    private Node deleteMax(Node x) {
        if (x.right == null) {
            return x.left;
        }
        x.right = deleteMax(x.right);
        x.n = size(x.left) + size(x.right) + 1;
        return x;
    }

    private void delete(Key key) {
        root = delete(root, key);
    }

    private Node delete(Node x, Key key) {
        if (x == null) {
            return null;
        }
        int cmp = key.compareTo(x.key);
        if (cmp < 0) {
            x.left = delete(x.left, key);
        } else if (cmp > 0) {
            x.right = delete(x.right, key);
        } else {
            // 找到被删除的节点,如果只有一个子节点,或者没有子节点的情况
            if (x.right == null) {
                return x.left;
            }
            if (x.left == null) {
                return x.right;
            }
            // 有两个子节点,找后继节点
            Node t = x;
            x = min(t.right);
            x.right = deleteMin(t.right);
            x.left = t.left;
        }
        x.n = size(x.left) + size(x.right) + 1;
        return x;
    }

    public Iterable<Key> keys(){
        return keys(min(), max());
    }

    /**
     * 查找在给定范围的key
     * @param lo
     * @param hi
     * @return
     */
    public Iterable<Key> keys(Key lo, Key hi) {
        Queue<Key> queue = new LinkedList<>();
        keys(root, queue, lo, hi);
        return queue;
    }

    /**
     * 使用中序遍历
     * @param x
     * @param queue
     * @param lo
     * @param hi
     */
    private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
        if (x == null) {
            return;
        }
        int cmplo = lo.compareTo(x.key);
        int cmphi = hi.compareTo(x.key);
        if (cmplo < 0) {
            keys(x.left, queue, lo, hi);
        }
        if (cmplo <= 0 && cmphi >= 0) {
            // 如果在指定范围内,加入队列
            queue.offer(x.key);
        }
        if (cmphi > 0) {
            keys(x.right, queue, lo, hi);
        }
    }

    public static void main(String[] args) {
        BST<String, Integer> st = new BST<>();

        st.put("A", 3);
        st.put("Z", 5);
        st.put("T", 8);
        st.put("S", 4);
        st.put("E", 1);

        System.out.println();

        for (String s : st.keys()) {
            System.out.println(s + " " + st.get(s));

        }
        System.out.println(st.size());
        System.out.println("max=" + st.max() + ",min=" + st.min());
    }
}

分析:

二叉查找树的形状取决于键被插入的先后顺序。

在最好的情况下,一颗含N个节点的树是完全平衡的,每条空链接和根节点的距离都为~lgN。

在最坏的情况下,搜索路径可能有N个节点。

二叉查找树和快速排序其实类似,树的根节点就是快速排序的第一个切分元素(左侧的键都比它小,右侧的键都比它大)。

 

虽然二叉查找树性能以及足够好了,但是在最坏的情况下还是比较糟糕。

为了保证一颗二分查找树,无论怎么构造它,它的运行时间都是对数级(lgN)别的,我们需要用到另一种查找树:平衡查找树

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值