查找(二叉查找數)

二叉查找數

定義

一個二叉查找數(BST)是一顆二叉樹,其中每個結點都含有一個Comparable的鍵以及相關聯的值,且每個結點的鍵都大于其左子樹的任意結點的鍵,小于其右子樹的任意結點的鍵

基本實現

  1. 數據表示
    鍵+ 值+ 左鏈接+右鏈接+結點計數器
  2. 查找
    如果樹是空的,則查找未命中;如果查找的鍵小于當前結點的鍵,則繼續查找其左子樹;如果查找的鍵大于當前結點的鍵,則繼續查找其右子樹;如果查找的鍵等于當前結點的鍵,則查找命中。
  3. 插入
    如果樹是空的,則返回一個含有該鍵值對的新結點。如果被查找的鍵小于根節點的鍵,則繼續在左子樹中插入該鍵,否則在右子樹中插入該鍵。(記得更新節點計數器)
  4. 性能分析
    二叉查找數的算法的運行時間依賴于樹的形狀,而樹的形狀則取決于鍵的插入順序。如果插入一個有序的鍵值對,則會出現最壞情況。
    由N個隨機鍵構造的二叉查找數,查找命中和插入命中的平均所需比較次數為~2lnN(1.39lgN)。
    二叉查找和插入的成本都是對數級別的(相比于二分查找的優勢)

有序性相關的方法實現

  1. 最大鍵和最小鍵
    最小鍵:如果左鏈接為空,最小鍵就是根節點;如果左鏈接非空,最小鍵就是左子樹的最小鍵
    最大鍵:如果右鏈接為空,最大鍵就是根節點;如果右鏈接非空,最大鍵就是右子樹的最大鍵
  2. 向上取整和向下取整
    floor(key) : key等于二叉樹的根節點,返回根節點的值
    2.1 key小于二叉樹的根節點,floor(key)一定在根節點的左子樹中
    2.2 key大于二叉樹的根節點
    2.2.1 右子樹中存在小于等于key的結點時,floor(key)在右子樹中
    2.2.2 否則返回根節點
  3. 選擇操作
    查找排名為k的鍵(樹中有k個小于它的節點),假設左子樹的結點數為t
    3.1 如果t > k 在左子樹中查找排名為k的鍵
    3.2 如果t < k 在右子樹中查找排名為k-t-1的鍵
    3.3 如果t=k 返回根節點
  4. 排名
    返回給定鍵在樹中的排名
    4.1 如果給定的鍵小于根節點的鍵,則返回該鍵在左子樹中的排名
    4.2 如果給定的鍵大于根節點的鍵,則返回該鍵在右子樹中的排名+左子樹的節點個數+1
    4.3 如果給定的鍵等于根節點的鍵,則返回左子樹的節點個數
  5. 刪除最大值和最小值
    deleteMin():如果左子樹不為空,則要刪除的節點在左子樹;如果左子樹為空,則返回該節點的右子樹
  6. 刪除操作
    如果某個節點左右子樹都不為空,刪除節點以后,會產生兩個子樹而被刪除的節點的父節點只空出了一個鏈接。可以使用被刪除節點的后繼節點(右子樹中的最小節點)補充它的位置。
    6.1 將指向即將被刪除的結點的鏈接保存為t
    6.2 將x指向它的后繼結點min(t.right)
    6.3 將x的右鏈接指向deleteMin(t.right)
    6.4 將x的左鏈接設為t.left

代碼實現

package edu.princeton.cs.algs4.chapter3;

/**
 * 使用二叉查找樹實現的符號表
 * Created by tianxianhu on 2017/3/6.
 */
public class BST<Key extends Comparable<Key>, Value> {
    private Node root;
    private class Node{
        private Key key;
        private Value val;
        private Node left;
        private Node 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;
        else return x.N;
    }

    /**
     * 獲取指定鍵的值,從根節點開始,比較鍵值
     * 小于當前鍵值則在左子樹中繼續尋找,大于則在右子樹中繼續尋找,等于則返回當前節點的值
     * @param key
     * @return
     */
    public Value get(Key key) {
        return get(root, key);
    }

    private Value get(Node x, Key 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;
        }
    }

    /**
     * 尋找key,找到則更新它的值,否則為它創建一個新節點,同時更新節點數量
     * @param key
     * @param val
     * @return
     */
    public Node put(Key key, Value val) {
        return put(root, key, val);
    }

    private Node put(Node x, Key key, Value val) {
        // 如果不存在,新建節點插入到字數當中
        if (x == null)
            return new Node(key, val, 1);
        int cmp = key.compareTo(x.key);
        // 不存在,則繼續在左/右子樹上尋找
        if (cmp < 0) {
            x.left = put(x.left, key, val);
        } else if (cmp > 0) {
            x.right = put(x.right, key, val);
        } else {
            // 存在,則更新值
            x.val = val;
        }
        // 增加了節點,更新每個子樹的數量
        x.N = size(x.left) + size(x.right) + 1;
        return x;
    }

    public Key min() {
        return min(root).key;
    }

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

    /**
     * 將要查詢的鍵值和根節點的鍵值進行比較
     * 1. 與根節點鍵值相等,返回當前節點的鍵
     * 1. 小于根節點鍵值,則在左子樹中繼續尋找
     * 2. 大于根節點鍵值,則在右子樹中尋找(可能在右子樹中,也可能是根節點)
     * 2.1 如果右子樹中尋找返回null,則返回根節點
     * 2.2 如果在右子樹中尋找到,則返回找到的節點
     * @param key
     * @return
     */
    public Key floor(Key key) {
        Node x = floor(root, key);
        if (x == null)
            return null;
        return x.key;
    }

    private Node floor(Node x, Key key) {
        if (x == null)
            return null;
        int cmp = key.compareTo(x.key);
        // 相等,返回當前值
        if (cmp == 0)
            return x;
        // 小于根節點,在左子樹當中尋找
        if (cmp < 0)
            return floor(x.left, key);
        // 大于根節點,在右子樹中尋找
        Node t = floor(x.right, key);
        // 存在則返回該節點,不存在則返回根節點
        if (t != null)
            return t;
        else
            return x;
    }

    /**
     * 查詢排名為k的鍵(0-based)
     * 計算做子樹的節點數量t,與排名k進行比較
     * 1. 如果t>k, 就繼續在左子樹中查找排名為k的鍵
     * 2. 如果k>t, 就在右子樹中查找排名為k-t-1的鍵
     * 3. 如果k=t, 就返回當前節點
     * @param k
     * @return
     */
    public Key select(int k) {
        return select(root, k).key;
    }

    private Node select(Node x, int k) {
        if (x == null)
            return null;
        int t = size(x.left);
        if (k < t) {
            return select(x.left, k);
        } else if (t < k) {
            return select(x.right, k - t - 1);
        } else {
            return x;
        }
    }

    /**
     * 查詢鍵key的排名
     * 將要查詢的鍵與當前的鍵值進行比較
     * 1.如果相等,則返回根節點左子樹的節點數
     * 2.小于根節點,則返回該鍵在左子樹中的排名
     * 3.大于根節點,則返回size(x.left)+1+右子樹中的排名
     * @param key
     * @return
     */
    public int rank(Key key) {
        return rank(root, key);
    }

    private int rank(Node x, Key key) {
        if (x == null)
            return 0;

        int cmp = key.compareTo(x.key);
        if (cmp < 0)
            return rank(x.left, key);
        else if (cmp > 0)
            return 1 + size(x.left) + rank(x.right, key);
        else
            return size(x.left);
    }

    /**
     * 1.不斷檢索左子樹,直到遇見空的左子樹
     * 2.返回該節點的右鏈接
     * 3.遞歸調用結束后更新節點計數器
     */
    public void deleteMin() {
        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 delete(Key key) {
        root = delete(root, key);
    }

    /**
     * 1.尋找到要刪除節點t的后繼節點min(t.right)
     * 2.將后繼節點x.right指向deleteMin(x.right),即刪除后繼節點以后的子樹,該子樹大于后繼節點
     * 3.將后繼節點x.left指向t.left
     * @param x
     * @param key
     * @return
     */
    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.left == null)
                return x.right;
            if (x.right == null)
                return x.left;
            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;
    }

}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容