數據結構之二叉查找樹

定義:二叉查找樹(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)別的,我們需要用到另一種查找樹:平衡查找樹

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章