數(shù)據(jù)結(jié)構(gòu)與算法(八),查找

前面介紹了基本的排序算法,排序通常是查找的前奏操作。這篇介紹基本的查找算法。

目錄:

  • 1、符號(hào)表
  • 2、順序查找
  • 3、二分查找
  • 4、插值查找
  • 5、二叉查找樹(shù)
  • 6、平衡查找樹(shù)
  • 6.1、平衡二叉樹(shù)(AVL樹(shù))
  • 6.2、2-3查找樹(shù)
  • 6.3、紅黑樹(shù)
  • 7、性能比較

1、符號(hào)表

符號(hào)表(Symbol Table)是一種存儲(chǔ)鍵值對(duì)的數(shù)據(jù)結(jié)構(gòu),它可以將鍵和值關(guān)聯(lián)起來(lái)。支持兩種操作:插入,將一組新的鍵值對(duì)插入到表中;查找,根據(jù)給定的鍵得到響應(yīng)的值。

符號(hào)表,有時(shí)又稱(chēng)索引,是為了加快查找速度而設(shè)計(jì)。它將關(guān)鍵字Key和記錄Value相關(guān)聯(lián),通過(guò)關(guān)鍵字Key來(lái)查找記錄Value。在現(xiàn)實(shí)生活中,我們經(jīng)常會(huì)遇到各種需要根據(jù)key來(lái)查找value的情況,比如DNS根據(jù)域名查找IP地址,圖書(shū)館根據(jù)索引號(hào)查找圖書(shū)等等:

符號(hào)表的特征:

  • 表中不能有重復(fù)的鍵
  • 鍵和值不能為空

符號(hào)表的抽象數(shù)據(jù)類(lèi)型:

public interface ST<K, V> {
    //將鍵值對(duì)存入表中
    void put(K key, V value);
    //獲取key對(duì)應(yīng)的值
    V get(K key);
}

2、順序查找

順序查找(Sequential Search)又稱(chēng)線性查找,是最基本的查找技術(shù)。從表中第一個(gè)記錄開(kāi)始,逐個(gè)進(jìn)行查找,若記錄的關(guān)鍵字和給定值相等,則查找成功。若直到最后,沒(méi)有關(guān)鍵字和給定值相等,則查找失敗。

代碼:

public class SequentialST<K, V> implements ST<K, V>{
    private Node head;
    private class Node {
        K key;
        V value;
        Node next;
        public Node(K key, V value, Node next) {
            super();
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }

    @Override
    public void put(K key, V value) {
        Node temp = sequentialSearch(key);
        if(temp != null) {
            temp.value = value;
        }else {
            head = new Node(key, value, head);
        }
    }
    
    //順序查找,【關(guān)鍵】
    private Node sequentialSearch(K key) {
        for(Node cur= head; cur != null; cur=cur.next) {
            if(key.equals(cur.key)) {
                return cur;
            }
        }
        return null;
    }

    @Override
    public V get(K key) {
        Node temp = sequentialSearch(key);
        if(temp != null) {
            return temp.value;
        }
        return null;
    }
    
    public static void main(String[] args) {
        SequentialST<String, Integer> st = new SequentialST<>();
        st.put("AA", 2);
        st.put("BB", 2);
        System.out.println(st.get("BB"));
    }
}

很顯然順序查找的時(shí)間復(fù)雜度為O(N),效率非常低。

3、二分查找

二分查找(Binary Search),又稱(chēng)折半查找。二分查找的前提是符號(hào)表中的記錄必須有序。在符號(hào)表中取中間記錄作為比較對(duì)象,若中間值和給定值相等,則查找成功;若給定值小于中間值,則在左半?yún)^(qū)繼續(xù)查找,否則在右半?yún)^(qū)進(jìn)行查找;不斷重復(fù)直到成功或失敗。

代碼:

/**
 *基于二分查找的符號(hào)表 
 */
public class BinarySearchST<K extends Comparable<K>, V> 
    implements ST<K, V> {
    
    private K[] keys;
    private V[] values;
    private int size;
    
    public BinarySearchST(int capacity) {
        keys = (K[]) new Comparable[capacity];
        values = (V[]) new Object[capacity];
    }

    @Override
    public void put(K key, V value) {
        int i = binarySearch(key, 0, size-1);
        //查找到給定的鍵,則更新對(duì)應(yīng)的值, size=0時(shí),i=0
        if(i < size && keys[i].compareTo(key) == 0) {
            values[i] = value;
            return;
        }
        for(int j=size; j>i; j--) {
            keys[j] = keys[j-1];
            values[j] = values[j-1];
        }
        keys[i] = key;
        values[i] = value;
        size++;
    }

    @Override
    public V get(K key) {
        int i = binarySearch(key, 0, size-1);
        if(keys[i].compareTo(key) == 0) {
            return values[i];
        }
        return null;
    }
    
    //二分查找,【關(guān)鍵】
    private int binarySearch(K key, int down, int up) {
        while(down <= up) {
            int mid = down + (up-down)/2;
            int temp = keys[mid].compareTo(key);
            if(temp > 0) {
                up = mid-1;
            }else if(temp < 0) {
                down = mid + 1;
            } else {
                return mid;
            }
        }
        return down;
    }
    
    public static void main(String[] args) {
        BinarySearchST<String, Integer> st = new BinarySearchST<>(10);
        st.put("AA", 2);
        st.put("BB", 2);
        System.out.println(st.get("BB"));
    }
}

二分查找的時(shí)間復(fù)雜度為O(logN)

4、插值查找

插值查找(Interpolation Search)是根據(jù)要查找的關(guān)鍵字key與查找表中最大最小記錄的關(guān)鍵字比較后的查找方法。其前提條件是符號(hào)表有序。

插值查找的關(guān)鍵是將二分查找中

代碼:

private int binarySearch(K key, int down, int up) {
    while(down <= up) {
        int mid = down + (key-keys[down])/(keys[up]-keys[down])*(up-down);
        int temp = keys[mid].compareTo(key);
        if(temp > 0) {
            up = mid-1;
        }else if(temp < 0) {
            down = mid + 1;
        } else {
            return mid;
        }
    }
    return down;
}

對(duì)于表長(zhǎng)較大,且關(guān)鍵字分布比較均勻的符號(hào)表,插值查找的性能比二分查找要好的多。

5、二叉查找樹(shù)

二叉查找樹(shù)(Binary Search Tree),又稱(chēng)二叉排序樹(shù)。它是一棵二叉樹(shù),其中每個(gè)結(jié)點(diǎn)的鍵都大于其左子樹(shù)中任意結(jié)點(diǎn)的鍵而小于其右子樹(shù)中任意結(jié)點(diǎn)的鍵。

代碼:

//二叉查找樹(shù)
public class BST <K extends Comparable<K>, V> 
    implements ST<K, V>  {
    private Node root; //二叉樹(shù)的根結(jié)點(diǎn)
    
    private class Node {
        K key;  //鍵
        V value; //值
        Node left, right; //左右子樹(shù)
        int N; //以該結(jié)點(diǎn)為根的結(jié)點(diǎn)總數(shù)
        public Node(K key, V value, int n) {
            this.key = key;
            this.value = value;
            N = n;
        }
    }

    @Override
    public void put(K key, V value) {
        root = put(root, key, value);
    }
    
    //插入操作
    private Node put(Node node, K key, V value) {
        if(node == null) 
            return new Node(key,value,1);
        int cmp = key.compareTo(node.key);
        if(cmp < 0) {
            node.left = put(node.left, key, value);
        }else if(cmp > 0) {
            node.right = put(node.right, key, value);
        } else {
            node.value = value;
        }
        node.N = node.left.N + node.right.N + 1; //遞歸返回時(shí)更新N
        return node;
    }

    @Override
    public V get(K key) {
        return get(root, key);
    }
    
    //查找操作
    private V get(Node node, K key) {
        if(node == null)
            return null;
        int cmp = key.compareTo(node.key);
        if(cmp < 0) {
             return get(node.left, key);
        }else if(cmp > 0) {
            return get(node.right, key);
        } else {
            return node.value;
        }
    }
}

插入過(guò)程:

BST插入元素

在插入操作中,若樹(shù)為空,就返回一個(gè)含有該鍵值對(duì)的新結(jié)點(diǎn),若查找的鍵小于根結(jié)點(diǎn),則在左子樹(shù)中插入該鍵,否則在右子樹(shù)中插入該鍵。這樣通過(guò)遞歸的方法就能構(gòu)造出一個(gè)二叉查找樹(shù)。

查找過(guò)程:

BST查找元素

對(duì)于查找操作可以使用非遞歸的方法來(lái)提高性能。其代碼為:

@Override
public V get(K key) {
    Node node = root;
    while(node != null) {
        int cmp = key.compareTo(node.key);
        if(cmp == 0) {
            return node.value;
        }else if(cmp > 0) {
            node = node.right;
        }else {
            node = node.left;
        }
    }
    return null;
}

刪除操作

若要?jiǎng)h除的結(jié)點(diǎn)是二叉樹(shù)中的葉子結(jié)點(diǎn),刪除它們對(duì)整棵樹(shù)無(wú)影響,直接刪除即可。若刪除的結(jié)點(diǎn)是只有左子樹(shù)或右子樹(shù),將它的左子樹(shù)或右子樹(shù)整個(gè)移動(dòng)到刪除結(jié)點(diǎn)的位置即可。如刪除二叉樹(shù)中最小結(jié)點(diǎn)的過(guò)程:

刪除最小結(jié)點(diǎn).png

若要?jiǎng)h除的的結(jié)點(diǎn) 既有左子樹(shù)又有右子樹(shù),只需找到要?jiǎng)h除結(jié)點(diǎn)的直接前驅(qū)或直接后繼(即左上樹(shù)的最大結(jié)點(diǎn)或右子樹(shù)的最小結(jié)點(diǎn))S,用S來(lái)替換它 ,然后刪除結(jié)點(diǎn)S即可。如刪除帶左右子樹(shù)的結(jié)點(diǎn)2的過(guò)程:

刪除結(jié)點(diǎn).png

代碼如下:

//刪除鍵key及其對(duì)應(yīng)的值
public void delete(K key) {
    root = delete(root, key);
}

private Node delete(Node node, K key) {
    if(node == null) 
        return null;
    int cmp = key.compareTo(node.key);
    if(cmp < 0) {
        node.left = delete(node.left, key);
    }else if(cmp > 0) {
        node.right = delete(node.right, key);
    } else {
        if(node.left == null) {
            return node.right;
        }
        if(node.right == null) {
            return node.left;
        }
        Node temp = node;
        node = min(temp.right);
        node.right = deleteMin(temp.right);
        node.left = temp.left;
    }
    node.N = node.left.N + node.right.N + 1; //從棧返回時(shí)更新N
    return node;
}

//刪除一個(gè)子樹(shù)的最小結(jié)點(diǎn)
private Node deleteMin(Node node) {
    if(node.left == null) { //刪除結(jié)點(diǎn)node
        return node.right;
    }
    node.left = deleteMin(node.left);
    node.N = node.left.N + node.right.N + 1; //更新子樹(shù)的計(jì)數(shù)N
    return node;
}

//查找一個(gè)子樹(shù)的最小結(jié)點(diǎn)
private Node min(Node node) {
    if(node.left == null) {
        return node;
    }
    return min(node.left);
}

二叉查找樹(shù)的性能:

二叉查找樹(shù)的性能取決于樹(shù)的形狀,而樹(shù)的形狀取決于鍵被插入的順序。最好情況下,二叉樹(shù)是完全平衡的,此時(shí)查找和插入的時(shí)間復(fù)雜度都為O(logN)。最壞情況下,二叉樹(shù)呈線型,此時(shí)查找和插入的時(shí)間復(fù)雜度都為O(N)。平均情況下,時(shí)間復(fù)雜度為O(logN)。

6、平衡查找樹(shù)

雖然二叉查找樹(shù)能夠很好的用于許多應(yīng)用中,但它在最壞情況下的性能很糟糕。而平衡查找樹(shù)的所有操作都能夠在對(duì)數(shù)時(shí)間完成。

1、平衡二叉樹(shù)(AVL樹(shù))

平衡二叉樹(shù)(Self-Balancing Binary Search Tree),是一種二叉排序樹(shù),其中每一個(gè)節(jié)點(diǎn)的左子樹(shù)和右子樹(shù)的高度差至多等于1。將二叉樹(shù)上結(jié)點(diǎn)的左子樹(shù)深度減去右子樹(shù)深度的值稱(chēng)為平衡因子BF(Balance Factor)。平衡二叉樹(shù)的BF值只能為-1,0,1。

平衡二叉樹(shù).png

第一幅圖是平衡二叉樹(shù)。在第二幅圖中結(jié)點(diǎn)2的左結(jié)點(diǎn)比結(jié)點(diǎn)2大,所以它不是二叉排序樹(shù),而平衡二叉樹(shù)的前提是一個(gè)二叉排序樹(shù)。在第三幅圖中結(jié)點(diǎn)5的左子樹(shù)的高度為2,右子樹(shù)高度為0,BF值為2,不符合平衡二叉樹(shù)的定義。

平衡二叉樹(shù)的構(gòu)建思想:在構(gòu)建二叉查找樹(shù)的過(guò)程中,每當(dāng)插入一個(gè)結(jié)點(diǎn)時(shí),先檢查是否因插入而破壞了樹(shù)的平衡性,若是,則找出最小不平衡子樹(shù),調(diào)整最小不平衡子樹(shù)中各結(jié)點(diǎn)之間的鏈接關(guān)系,進(jìn)行相應(yīng)的旋轉(zhuǎn),使之成為新的平衡子樹(shù)。

對(duì)不平衡子樹(shù)的操作有左旋和右旋。

左旋:

左旋

代碼:

private Node rotateLeft(Node h) {
    Node x = h.right;
    h.right = x.left;
    x.left = h;
    x.N = h.N;
    h.N = h.left.N + h.right.N + 1;
    return x;
}

右旋:

右旋

代碼:

private Node rotateRight(Node h) {
    Node x = h.left;
    h.left = x.right;
    x.right = h;
    x.N = h.N;
    h.N = h.left.N + h.right.N + 1;
    return x;
}

向一棵AVL樹(shù)中插入一個(gè)結(jié)點(diǎn)的四種情況:

  • 第一種:當(dāng)一個(gè)結(jié)點(diǎn)的BF值大于等于2,并且它的子結(jié)點(diǎn)的BF值為正,則右旋
  • 第二種:當(dāng)一個(gè)結(jié)點(diǎn)的BF值大于等于2,但它的子結(jié)點(diǎn)的BF值為負(fù),對(duì)子結(jié)點(diǎn)進(jìn)行左旋操作,變?yōu)榈谝环N情況,然后再進(jìn)行處理。
  • 第三種:當(dāng)一個(gè)結(jié)點(diǎn)的BF值小于等于-2,并且它的子結(jié)點(diǎn)的BF值為負(fù),則左旋
  • 第四種:當(dāng)一個(gè)結(jié)點(diǎn)的BF值小于等于-2,但它的子結(jié)點(diǎn)的BF值為正,對(duì)子結(jié)點(diǎn)進(jìn)行右旋操作,變?yōu)榈谌N情況,然后再進(jìn)行處理。

AVL的實(shí)現(xiàn):

//平衡二叉樹(shù)(AVL樹(shù))的實(shí)現(xiàn)
public class AVL<K extends Comparable<K>, V> implements ST<K, V> {
    private Node root; // 二叉樹(shù)的根結(jié)點(diǎn)

    private class Node {
        K key; // 鍵
        V value; // 值
        Node left, right; // 左右子樹(shù)
        int N; // 以該結(jié)點(diǎn)為根的結(jié)點(diǎn)總數(shù)

        public Node(K key, V value, int n) {
            this.key = key;
            this.value = value;
            N = n;
        }
    }

    // 左旋
    private Node rotateLeft(Node h) {
        Node x = h.right;
        h.right = x.left;
        x.left = h;
        x.N = h.N;
        h.N = h.left.N + h.right.N + 1;
        return x;
    }

    // 右旋
    private Node rotateRight(Node h) {
        Node x = h.left;
        h.left = x.right;
        x.right = h;
        x.N = h.N;
        h.N = h.left.N + h.right.N + 1;
        return x;
    }

    @Override
    public void put(K key, V value) {
        root = put(root, key, value);
    }

    // 插入操作
    private Node put(Node node, K key, V value) {
        if (node == null)
            return new Node(key, value, 1);
        int cmp = key.compareTo(node.key);
        if (cmp < 0) {
            node.left = put(node.left, key, value);
        } else if (cmp > 0) {
            node.right = put(node.right, key, value);
        } else {
            node.value = value;
        }

        if (BF(node) <= -2) {
            if (BF(node.right) > 0) {
                rotateRight(node.right);
            }
            rotateLeft(node);
        }
        if (BF(node) >= 2) {
            if (BF(node.left) < 0) {
                rotateLeft(node);
            }
            rotateRight(node);
        }

        node.N = node.left.N + node.right.N + 1; // 從棧返回時(shí)更新N
        return node;
    }

    // 平衡因子BF的值
    private int BF(Node node) {
        return depth(node.left) - depth(node.right);
    }

    // 求子樹(shù)的深度
    private int depth(Node node) {
        if (node == null)
            return 0;
        return Math.max(depth(node.right), depth(node.left)) + 1;
    }

    // 查找操作,和二叉查找樹(shù)相同
    @Override
    public V get(K key) {
        Node node = root;
        while (node != null) {
            int cmp = key.compareTo(node.key);
            if (cmp == 0) {
                return node.value;
            } else if (cmp > 0) {
                node = node.right;
            } else {
                node = node.left;
            }
        }
        return null;
    }
}

2、2-3查找樹(shù)

一棵2-3查找樹(shù),或?yàn)橐豢每諛?shù),或由以下結(jié)點(diǎn)組成:

  • 2-結(jié)點(diǎn):含有一個(gè)鍵和兩個(gè)鏈接,左鏈接指向鍵小于該結(jié)點(diǎn)的子樹(shù),右鏈接指向鍵大于該結(jié)點(diǎn)的子樹(shù)。
  • 3-結(jié)點(diǎn):含有兩個(gè)鍵和三個(gè)鏈接,左鏈接指向鍵都小于該結(jié)點(diǎn)的子樹(shù),中鏈接指向鍵位于該結(jié)點(diǎn)兩個(gè)鍵之間的子樹(shù),右鏈接指向鍵大于該結(jié)點(diǎn)的子樹(shù)。
2-3樹(shù)

查找:

2-3樹(shù)的查找操作和平衡二叉樹(shù)一樣,從根結(jié)點(diǎn)開(kāi)始,根據(jù)比較結(jié)果,到相應(yīng)的子樹(shù)中去繼續(xù)查找,直到命中或查找失敗。

插入:

向一棵2-3樹(shù)中插入一個(gè)結(jié)點(diǎn)可能的情況

  • 第一種:向2-結(jié)點(diǎn)中插入新鍵,只需用一個(gè)3-結(jié)點(diǎn)替換2-結(jié)點(diǎn)即可。
  • 第二種:向3-結(jié)點(diǎn)中插入新鍵,這個(gè)3-結(jié)點(diǎn)沒(méi)有父結(jié)點(diǎn),此時(shí)可將其分解為一個(gè)含有3個(gè)結(jié)點(diǎn)的二叉查找樹(shù)。
  • 第三種:向一個(gè)父結(jié)點(diǎn)為2-結(jié)點(diǎn)的3-結(jié)點(diǎn)中插入新鍵,先將其分解為一個(gè)含有3個(gè)結(jié)點(diǎn)的二叉查找樹(shù),然后將中鍵移到父結(jié)點(diǎn),父結(jié)點(diǎn)由2-結(jié)點(diǎn)變?yōu)?-結(jié)點(diǎn)。
  • 第四種:向一個(gè)父結(jié)點(diǎn)為3-結(jié)點(diǎn)的3-結(jié)點(diǎn)中插入新鍵,和第三種情況一樣,一直向上分解臨時(shí)的4-結(jié)點(diǎn),直到遇到一個(gè)2-結(jié)點(diǎn)將它替換為3-結(jié)點(diǎn),或到達(dá)3-結(jié)點(diǎn)的根,然后直接分解成一個(gè)含有3個(gè)結(jié)點(diǎn)的二叉查找樹(shù)。如圖,此時(shí)2-3樹(shù)依然平衡。

性能:

2-3查找樹(shù)的插入和查找操作的時(shí)間復(fù)雜度不超過(guò) O(logN)。

2-3查找樹(shù)需要維護(hù)兩種不同的結(jié)點(diǎn),實(shí)現(xiàn)起來(lái)比較復(fù)雜,并且在結(jié)點(diǎn)的轉(zhuǎn)換過(guò)程中需要大量的復(fù)制操作,這些都將產(chǎn)生額外的開(kāi)銷(xiāo),使的算法的性能可能比二叉查找樹(shù)要慢。

3、紅黑樹(shù)

紅黑樹(shù)是對(duì)2-3查找樹(shù)的一種改進(jìn),它使用標(biāo)準(zhǔn)的二叉查找樹(shù)和一些額外的信息來(lái)表示2-3樹(shù)。使用兩個(gè)2-結(jié)點(diǎn)和『紅鏈接』來(lái)表示一個(gè)3-結(jié)點(diǎn)。由于每個(gè)結(jié)點(diǎn)都只有一個(gè)指向自己的鏈接,所以可以在結(jié)點(diǎn)中使用boolean值來(lái)表示紅鏈接。

紅黑樹(shù),是滿(mǎn)足下列條件的二叉樹(shù):

  • 紅鏈接均為左鏈接,即紅色結(jié)點(diǎn)必須為左結(jié)點(diǎn)。
  • 沒(méi)有任何結(jié)點(diǎn)同時(shí)和兩個(gè)紅鏈接相連,即不存在左右子結(jié)點(diǎn)都為紅的結(jié)點(diǎn)。
  • 任何空鏈接到根節(jié)點(diǎn)的路徑上的黑鏈接(黑結(jié)點(diǎn))數(shù)量相同。
  • 根結(jié)點(diǎn)總是黑色的。

2-3查找樹(shù)與紅黑樹(shù)的對(duì)應(yīng)關(guān)系

紅黑樹(shù)的左旋和右旋操作,與平衡二叉樹(shù)(AVL樹(shù))的基本相同,只需注意旋轉(zhuǎn)后的結(jié)點(diǎn)顏色的變化即可。其代碼等下會(huì)給出。

向一個(gè)2-結(jié)點(diǎn)中插入結(jié)點(diǎn)的情況:

向一個(gè)2-結(jié)點(diǎn)中插入結(jié)點(diǎn)比較簡(jiǎn)單,和平衡二叉樹(shù)基本相同,這里需要注意,第二種情況,由于紅黑樹(shù)的紅色結(jié)點(diǎn)必須為左結(jié)點(diǎn),所以這里需要一個(gè)左旋操作,將右結(jié)點(diǎn)變?yōu)樽蠼Y(jié)點(diǎn)。

向一個(gè)3-結(jié)點(diǎn)中插入結(jié)點(diǎn)的情況:

向一個(gè)3-結(jié)點(diǎn)中插入結(jié)點(diǎn),由紅黑樹(shù)的定義,結(jié)點(diǎn)不能和兩個(gè)紅色結(jié)點(diǎn)相連并且紅色結(jié)點(diǎn)必須為左結(jié)點(diǎn),所以需要做相應(yīng)的旋轉(zhuǎn)操作。這里需要注意第一種情況中,對(duì)左右結(jié)點(diǎn)均為紅色時(shí)的顏色轉(zhuǎn)換,處理后紅色向上傳遞,這可能會(huì)使上層結(jié)點(diǎn)的左右結(jié)點(diǎn)均為紅色,這時(shí)需要對(duì)上層繼續(xù)進(jìn)行顏色轉(zhuǎn)換,直到根結(jié)點(diǎn)或不出現(xiàn)左右結(jié)點(diǎn)均為紅的情況。

代碼實(shí)現(xiàn):

//紅黑樹(shù)的實(shí)現(xiàn)
public class RedBlackBST <K extends Comparable<K>, V> implements ST<K, V> {
    private static final boolean RED = true;
    private static final boolean BLACK = false;
    private Node root; // 二叉樹(shù)的根結(jié)點(diǎn)

    private class Node {
        K key; // 鍵
        V value; // 值
        Node left, right; // 左右子樹(shù)
        int N; // 以該結(jié)點(diǎn)為根的結(jié)點(diǎn)總數(shù)
        //由于每個(gè)結(jié)點(diǎn)都只有一個(gè)指向自己的鏈接,所以可以在結(jié)點(diǎn)中使用boolean值來(lái)表示紅鏈接。
        boolean color; 

        public Node(K key, V value, int n, boolean color) {
            this.key = key;
            this.value = value;
            N = n;
            this.color = color;
        }
    }
    
    private boolean isRed(Node node) {
        if(node == null) 
            return false;
        return node.color == RED;
    }

    // 左旋
    private Node rotateLeft(Node h) {
        Node x = h.right;
        h.right = x.left;
        x.left = h;
        x.color = h.color;
        h.color = RED;
        x.N = h.N;
        h.N = h.left.N + h.right.N + 1;
        return x;
    }
    
    // 右旋
    private Node rotateRight(Node h) {
        Node x = h.left;
        h.left = x.right;
        x.right = h;
        x.color = h.color;
        h.color = RED;
        x.N = h.N;
        h.N = h.left.N + h.right.N + 1;
        return x;
    }

    @Override
    public void put(K key, V value) {
        root = put(root, key, value);
        root.color = BLACK; //根節(jié)點(diǎn)總是黑色的,因?yàn)樗鼪](méi)有父鏈接
    }

    // 插入操作
    private Node put(Node node, K key, V value) {
        if (node == null)
            return new Node(key, value, 1,RED);
        int cmp = key.compareTo(node.key);
        if (cmp < 0) {
            node.left = put(node.left, key, value);
        } else if (cmp > 0) {
            node.right = put(node.right, key, value);
        } else {
            node.value = value;
        }

        if(isRed(node.right) && !isRed(node.left)) {
            node = rotateLeft(node);
        }
        if(isRed(node.left) && isRed(node.left.left)) {
            node = rotateRight(node);
        }
        if(isRed(node.left) && isRed(node.right)) {
            flipColors(node);
        }

        node.N = node.left.N + node.right.N + 1; // 從棧返回時(shí)更新N
        return node;
    }
    
    //顏色轉(zhuǎn)換
    private void flipColors(Node node) {
        node.color = RED;
        node.left.color = BLACK;
        node.right.color = BLACK;
    }

    // 查找操作,和二叉查找樹(shù)一樣
    @Override
    public V get(K key) {
        Node node = root;
        while (node != null) {
            int cmp = key.compareTo(node.key);
            if (cmp == 0) {
                return node.value;
            } else if (cmp > 0) {
                node = node.right;
            } else {
                node = node.left;
            }
        }
        return null;
    }
}

7、性能比較

性能如下圖:


關(guān)于紅黑樹(shù)的刪除,今天看了好久,也沒(méi)完全弄明白,這里就不寫(xiě)出來(lái)誤人子弟了,感興趣的可以看下這篇博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,497評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,305評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,962評(píng)論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,727評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,193評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,411評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,945評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,777評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,978評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,216評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,657評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,960評(píng)論 2 373

推薦閱讀更多精彩內(nèi)容