紅黑樹其實很簡單


1、背景

  在開發過程中免不了需要維護一組數據,並且要能夠快速地進行增刪改查。如果數據量很大並且需要持久化,那么就選擇數據庫。但如果數據量相對少一些不需要持久化並且對響應時間要求很高,那么直接存儲在本地內存中就最合適了。

2、鏈表

  將數據存儲在本地內存也不是隨便存的,需要按不同的場景選擇合適的數據結構。我們先來看下面這一組數據,54、24、74、12、42、62、83、9、17、33、49、58、65、79、90、5、20、29、37、46、69、86、95,需要選用一個數據結構存儲它們,並且要能支持快速的增刪改查操作。

  說起數據結構,一般人第一反應就是最簡單的線性結構,比如說數組、鏈表、棧或者隊列等。如果有某一個場景可以使用下標隨機訪問數據,那么毫無疑問數組是最合適的,畢竟是直接通過內存地址(指針)訪問數據,性能杠杠的。但實際開發中絕大多數場景都不可能提供下標訪問數據,所以這里我們就先使用一個雙向鏈表來看一下:

 

  雙向鏈表實現起來比較簡單,這里就直接上代碼了(鏈表不是重點,所以就只展示java的代碼,作為今天的開胃菜):

public class DoublyLinked<V> implements Iterable<E> {
    
    /*鏈表的頭和尾結點,不存儲數據只作為標記*/
    private final Node first, last;
    /*鏈表長度*/
    private int size;
    
    public DoublyLinked() {
        this.first = new Node(null, null, new Node());
        (this.last = first.next).prev = first;
    }
    
    /*鏈表中節點*/
    class Node {
        V value;
        Node prev, next;    
        public Node() {}
        public Node(V value, Node prev, Node next) {
            this.value = value;
            this.prev = prev;
            this.next = next;
        }
    }
    
    /*獲取鏈表長度*/
    public int size() { return size; }
    
    /*獲取鏈表頭結點節點*/
    public V peekFirst() {
        Node node = first.next;
        return last == node ? null : node.value;
    }
    
    /*獲取鏈表尾結點節點*/
    public V peekLast() {
        Node node = last.prev;
        return first == node ? null : node.value;
    }
    
    /*向鏈表頭部插入節點*/
    public void addFirst(V value) {
        Node first = this.first;
        first.next = first.next.prev = new Node(value, first, first.next);
        ++size;
    }
    
    /*向鏈表尾部插入節點*/
    public void addLast(V value) {
        Node last = this.last;
        last.prev = last.prev.next = new Node(value, last.prev, last);
        ++size;
    }
    
    /*刪除鏈表頭結點節點*/
    public V removeFirst() {
        Node first = this.first, node = first.next;
        if (last != node) {
            (first.next = node.next).prev = first;
            node.prev = node.next = null;
            --size;
        }
        return node.value;
    }
    
    /*刪除鏈表尾結點節點*/
    public V removeLast() {
        Node last = this.last, node = last.prev;
        if (first != node) {
            (last.prev = node.prev).next = last;
            node.prev = node.next = null;
            --size;
        }
        return node.value;
    }
    
    /*查找鏈表中的節點*/
    public boolean contains(V value) {
        for (Node node = first.next, last = this.last; last != node; node = node.next) {
            if (Objects.equals(value, node.value)) {
                return true;
            }
        }
        return false;
    }

    /*替換鏈表中的節點*/
    public boolean replace(V source, V target) {
        for (Node node = first.next, last = this.last; last != node; node = node.next) {
            if (Objects.equals(source, node.value)) {
                node.value = target;
                return true;
            }
        }
        return false;
    }
    
    /*替換鏈表中的節點*/
    public void replaceAll(V source, V target) {
        for (Node node = first.next, last = this.last; last != node; node = node.next) {
            if (Objects.equals(source, node.value)) {
                node.value = target;
            }
        }
    }

    /*刪除鏈表中的節點*/
    public boolean remove(V value) {
        for (Node prev = first, node = prev.next, last = this.last;
             last != node; node = (prev = node).next) {
            if (Objects.equals(value, node.value)) {
                (prev.next = node.next).prev = prev;
                node.prev = node.next = null;
                --size;
                return true;
            }
        }
        return false;
    }

    /*刪除鏈表中的節點*/
    public void removeAll(V value) {
        for (Node prev = first, node = prev.next, last = this.last;
             last != node; node = (prev = node).next) {
            if (Objects.equals(value, node.value)) {
                (prev.next = node.next).prev = prev;
                node.prev = node.next = null;
                node = prev;
                --size;
            }
        }
    }

    /*創建迭代器*/
    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            private Node current = DoublyLinked.this.first;
            
            @Override
            public boolean hasNext() {
                return DoublyLinked.this.last != current.next;
            }
            
            @Override
            public E next() {
                return (current = current.next).value;
            }
        };
    }
    
}

  上面的鏈表中數據的是無序且可重復的,其中有關首尾節點操作的方法時間復雜度都為O(1),但同時這些操作能適用的范圍也很窄。工作中大多數場景都是只知道具體的值然后操作鏈表,這時候更多的是使用contains、replace、remove等需要遍歷鏈表中節點的方法,如果維護的是一個值有序或者值去重的鏈表,則插入節點的方法也需要遍歷鏈表。如果每次遍歷查找節點都是最壞情況,也就是每次查找的節點都在鏈表尾端,這時候時間復雜度就為O(n),其中n為鏈表的長度。在數據量少的情況下是沒什么問題的,但是如果數據量很大鏈表很長,又遇到了高並發高吞吐量的場景,性能可能就不夠看了。

3、二叉查找(排序)樹

  鏈表的操作弊端很明顯,需要頻繁遍歷節點。但是遍歷這個工作又是省不了的,那么可以想辦法把需要遍歷的路徑變短,這時二叉查找(排序)樹就很符合這個需求了。我們先來看一下二叉查找樹的特點,首先其肯定是一棵二叉樹,其次樹中任意節點的關鍵字值一定大於其左子節點關鍵字值並且小於其右子節點關鍵字值,並且空樹也是一棵二叉查找(排序)樹。

  我們就以圖2作為例子,講解和演示二叉查找(排序)樹的查找、插入和刪除等操作。為了描述方便,后面我們稱呼節點時,直接使用關鍵字值代替。

3.1、查找

  假如我們需要查找關鍵字值為“37”的節點,那么操作步驟為:

  1.首先將“37”與根節點的關鍵字值“54”進行比較,發現“37”比“54”小,所以繼續與根節點左子節點的關鍵字值進行比較;

  2.比較之后發現“37”比“24”大,所以繼續與節點“24”的右子節點的關鍵字值進行比較;

  3.比較之后發現“37”比“42”小,所以繼續與節點“42”的左子節點的關鍵字值進行比較;

  4.比較之后發現“37”比“33”大,所以繼續與節點“33”的右子節點的關鍵字值進行比較;

  5.比較之后發現相等,所以直接返回該節點;

  這樣查找數據不需要再遍歷樹中的所有節點,只需要沿着每個節點的一個子節點往下遍歷就可以,大大縮短了查找路徑。理想情況下時間復雜度變為了O(log n),其時間增長曲線為一個對數函數。

  那么基於這樣的邏輯,查找最大值和最小值是不是也很簡單?這里就不多說了,大家自己試驗一下。

3.2、插入

  上面講解了怎么查找節點,這里繼續來看一下如何往樹中插入一個節點。

  假如我們插入關鍵字值為“52”的節點,那么操作步驟為:

  1.首先將“52”與根節點的關鍵字值“54”進行比較,發現“52”比“54”小,所以繼續與根節點左子節點的關鍵字值進行比較;

  2.比較之后發現“52”比“24”大,所以繼續與節點“24”的右子節點的關鍵字值進行比較;

  3.比較之后發現“52”比“42”大,所以繼續與節點“42”的右子節點的關鍵字值進行比較;

  4.比較之后發現“52”比“49”大,所以繼續與節點“49”的右子節點的關鍵字值進行比較;

  5.這時發現節點“49”右子節點是空節點,所以直接將插入節點作為節點“49”的右子節點插入到樹中;

  如果在執行上面的步驟過程中,發現插入節點的關鍵字值與其中某一個節點的關鍵字值相等,則直接作為修改操作,不需要再插入結點。比如插入關鍵字值為“65”的節點,大家可以自己腦補一下插入的步驟。

3.3、刪除

  刪除節點就比較麻煩一些,這里大概可以分為兩種情況。一種是刪除節點有兩個子節點,一種是刪除節點最多只有一個子節點。比如分別需要刪除節點“65”、“29”、“24”三個節點,其中節點“65”有一個子節點,節點“29”沒有子節點,節點“24”有兩個子節點。

  我們先看刪除節點“65”的步驟:

  1.首先需要找到關鍵字值為“65”的節點,怎么找就不需要再說一遍了吧;

  2.直接移除節點“65”;

  3.由於刪除節點“65”是其父節點“62”的右子節點,所以需要將其右子節點“69”作為其父節點“62”的右子節點,重新建立父子關系;

  刪除節點“29”就更簡單了,由於節點“29”沒有子節點,所以通過值找到節點之后可以直接刪除,不需要再建立父子關系。

  至於刪除節點“24”就比較麻煩了,由於其有兩個子節點,如果將其移除之后兩個子節點再與其父節點建立新的父子關系,那么就沒有辦法繼續維持二叉查找樹的結構了。這時就需要使用前繼節點或者后繼結點替換刪除節點,作為新的刪除節點被刪除。

  節點“24”的的前繼節點為節點“20”,后繼節點為節點“29”。大家看一下這三個節點之間都有什么關系?從值的大小來看,“20”是樹中所有關鍵字值中比“24”小的最大值,“29”是比“24”大的最小值。從樹的結構上來看,在水平衡軸上,節點“20”、“29”的兩個節點是最靠近節點“24”的,也就意味着節點“24”的前繼節點不可能有右子節點,后繼結點不可能有左子節點。那么我們轉換一下思路,假如將節點“24”移除之后,是否可以使用“20”、“29”這兩個節點的其中一個填充到原節點“24”的位置上呢?最終刪除節點“24”被轉換為了刪除節點“20”或者“29”了,問題是不是就解決了?至於如何查找前繼節點和后繼結點,這么簡單的事情,在這里就不多說了,大家可以自試一下。

3.4、代碼示例

  由於二叉查找(排序)樹並不是本文的重點,而且實現起來也很簡單,所以這里就只是用java代碼做演示。

public class BSTree<K extends Comparable<K>, V> {
    
    private Node root;
    private int size;
    
    /*獲取樹中節點個數*/
    public int size() { return size; }
    
    /*獲取根節點*/
    public Entry<K, V> getRoot() { return null == root ? null : new Entry<>(root.key, root.value); }
    
    /*判斷以給定節點為根節點的樹是否為一棵二叉查找樹*/
    public boolean isBSTree(Node node) {
        // 如果當前節點為空,則從根節點開始遞歸校驗
        node = null == node ? root : node;
        if (null == node) { // 空樹也是一棵二叉查找樹
            return true;
        }
        // 獲取當前節點的父節點、左子節點、右子節點
        Node parent = node.parent, nodeLeft = node.left, nodeRight = node.right;
        if (null != parent && parent.left != node && parent.right != node) {
            // 父節不為空,但是父節點的左右子節點引用都沒有指向當前節點,所以不是一棵二叉樹
            return false;
        }
        if (null != nodeLeft) { // 當前節點的左子節點不為空
            if (nodeLeft.parent != node || nodeLeft.compareTo(node) >= 0) {
                // 左子節點父節點引用沒有指向當前節點,不滿足是一棵二叉樹。
                // 或者左子節點的key值大於等於當前節點的key值,不滿足是一棵二叉查找樹(當前節點的key值大於左子節點小於右子節點)
                return false;
            }
            if (!isBSTree(nodeLeft)) { // 遞歸左子節點,判斷子樹是否滿足紅黑樹特性
                return false;
            }
        }
        if (null != nodeRight) { // 當前節點的右子節點不為空
            if (nodeRight.parent != node || nodeRight.compareTo(node) <= 0) {
                return false;
            }
            if (!isBSTree(nodeRight)) {
                return false;
            }
        }
        return true;
    }
    
    /*根據key查找value*/
    public V find(K key) {
        if (null == key) {
            throw new NullPointerException();
        }
        Node node = findNode(key, root);
        return null == node ? null : node.value;
    }
    
    /*查找Key值最小的節點*/
    public Entry<K, V> findMin() {
        Node node = root;
        while (null != node && null != node.left) {
            node = node.left;
        }
        return null == node ? null : new Entry<>(node.key, node.value);
    }
    
    /*查找Key值最大的節點*/
    public Entry<K, V> findMax() {
        Node node = root;
        while (null != node && null != node.right) {
            node = node.right;
        }
        return null == node ? null : new Entry<>(node.key, node.value);
    }
    
    /*插入一個節點*/
    public V put(K key, V value) {
        if (null == key) {
            throw new NullPointerException();
        }
        Node current = root, parent = null;
        // 獲取節點要插入的位置的父節點
        int compare = 0;
        while (null != current && 0 != (compare = key.compareTo(current.key))) {
            current = compare > 0 ? (parent = current).right : (parent = current).left;
        }
        if (null != current) { // 要插入的key已存在
            V ov = current.value;
            current.value = value;
            return ov;
        }
        if (null == parent) { // 要插入的樹為空樹
            this.root = new Node(key, value, null);
        } else { // 插入新節點
            if (compare < 0) {
                parent.left = new Node(key, value, parent);
            } else {
                parent.right = new Node(key, value, parent);
            }
        }
        ++size;
        return null;
    }
    
    /*刪除節點*/
    public V remove(K key) {
        if (null == key) {
            throw new NullPointerException();
        }
        Node remove, replace;
        if (null == (remove = findNode(key, root))) { // 需要刪除的節點在樹中找不到
            return null;
        }
        V value = remove.value;
        if (null != remove.left && null != (replace = remove.right)) {
            // 刪除節點的左右子節點都不為空節點,將刪除節點和后繼節點替換(也可以使用前繼節點替換刪除節點)
            while (null != replace.left) {
                replace = replace.left;
            }
            remove.key = replace.key;
            remove.value = replace.value;
            remove = replace;
        }
        // 此時最多只有一個子節點
        Node parent = remove.parent, child = null == (child = remove.left) ? remove.right : child; // 獲取父節點和子節點
        if (null != parent && null != child) { // 父節點不為空,並且有一個不為空的子節點
            (parent.left == remove ? parent.left = child : (parent.right = child)).parent = parent;
            remove.parent = remove.left = null;
        } else if (null != parent) { // 父節點不為空,並且子節點都為空
            remove.parent = parent.right == remove ? parent.right = null : (parent.left = null);
        } else if (null != child) { // 父節點為空,但是子節點不為空
            root = child;
            remove.left = remove.right = child.parent = null;
        } else { // 父節點和子節點都為空
            root = null;
        }
        --size;
        return value;
    }
    
    /*采用中序遍歷二叉樹,保證數據從小到大遍歷*/
    public void forEach(Consumer<Entry<K, V>> action) {
        Deque<Node> deque = new LinkedList<>();
        for (Node node = root; node != null || !deque.isEmpty(); ) {
            for (; node != null; node = node.left) {
                deque.push(node);
            }
            Node pop = deque.pop();
            action.accept(new Entry<>(pop.key, pop.value));
            node = pop.right;
        }
    }
    
    /*通用查找節點*/
    private Node findNode(K key, Node current) {
        Node found = null;
        for (int compare; null != current && null == found; ) {
            if ((compare = key.compareTo(current.key)) > 0) {
                current = current.right;
            } else if (compare < 0) {
                current = current.left;
            } else {
                found = current;
            }
        }
        return found;
    }
    
    class Node implements Comparable<Node> {
        K key;
        V value;
        Node parent, left, right;
        public Node(K key, V value, Node parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }
        @Override
        public int compareTo(Node node) { return key.compareTo(node.key); }
        
    }
    
    public static class Entry<K, V> {
        private final K key;
        private final V value;
        public Entry(K key, V value) {
            this.key = key;
            this.value = value;
        }
        public K getKey() { return key; }
        public V getValue() { return value; }
        @Override
        public String toString() { return "Entry{key=" + key + ", value=" + value + '}'; }
    }
    
}

  通過上面的分析,我們明白了二叉查找樹的原理,其查找遍歷節點路徑的長度取決於樹的高/深度。二叉樹中任何節點下左右子樹的高度差為節點的平衡因子,平衡因子越小樹就越平衡(絕對平衡的平衡二叉樹所有節點的平衡因子只會為-1、0、1),查找遍歷節點所能體現出來的性能也就越好。

  我們試想一下,如果圖2中的數據事先就已經排好序了,然后依次插入二叉查找樹中會有什么結果呢?假如數據是升序排列,那么插入樹中的值會越來越大,結果就是數據只會一直往右子樹中插入,最終樹就會退化為一個鏈表,平衡因子就為樹中節點的數量。刪除節點的時候也可能造成平衡因子不可預測的增長。

4、紅黑樹

  由於二叉查找樹在插入或者刪除節點的時候,有可能平衡因子會變的很大,甚至會直接退化為鏈表。最典型的就是插入有序數據的時候問題會很嚴重,但是在實際生產過程中,我們並不能避免插入的數據是有序的,這樣又會破壞樹的平衡性,所以就得想辦法在插入或者刪除節點之后對樹的平衡進行調整。能滿足這種特性的二叉樹我們稱其為自平橫二叉樹,目前業內已經存在很多很成熟的自平橫二叉查找樹。

  現在常用到的自平橫二叉查找樹大概有6中,分別為堆樹(Treap)、大小平衡樹(SBT)、伸展樹(Splay)、AVL樹(平衡二叉查找樹)、紅黑樹(Red-Black)、替罪羊樹(Scapegoat)等。他們有的是通過節點旋轉,有的是通過局部節點重組等操作進行自平橫。今天我們要介紹的當然就是這其中最亮的那個仔平衡樹中的扛把子  

  我們來看一下紅黑樹的特性,首先其肯定是一棵二叉查找樹,其次為了保持平衡其主要有以下5個特點:

  1. 每個節點的顏色是紅色或者黑色;
  2. 根節點顏色是黑色;
  3. 每個葉子結點的顏色都是黑色(這里的葉子節點是指不存在的空節點);
  4. 紅色節點的子節點都必須是黑色的(所有路徑中不允許出現連續的兩個紅色節點);
  5. 任意節點到其葉子結點的所有路徑上黑色節點的個數是相等的;

  紅黑樹和AVL樹不一樣,它並沒有顯式地聲明其平衡因子的范圍,但是通過其5個特性卻隱式地保證了樹中任意節點的左右子樹高度差絕對不會超過一倍,所以紅黑樹是一棵近似平衡的二叉查找樹。那么說道這一點,紅黑樹只是近似平衡,和AVL樹這種絕對平衡(任意節點左右子樹高度差絕對值不大於1)的樹相比,它憑什么更有優勢呢?答案就在自平衡的操作上。基於上面的5點特性,紅黑樹在自平衡的時候能盡可能少的處理更多的節點,並且其時間復雜度相對更加穩定。所以目前各領域中紅黑樹應用的相對較多,比如jdk中的HashMap(jdk1.8及以上版本)、TreeMap,STL中的map、set,Linux系統中的epoll等。

  上面講解了鏈表、二叉查找樹等,其實都只是作為拋磚引玉的作用,現在我們就要開始真正進入主題,詳細分析一下紅黑樹了。由於后面后的邏輯分析會有很多紅黑樹的局部子樹圖,所以這里先聲明一下圖中各種形狀節點的含義:

4.1、節點旋轉

  紅黑樹是通過節點的左右旋轉或者改變節點顏色進行自平橫的。改變節點節點顏色很好理解,在代碼里面無非就是一個屬性而已,這里就重點說一下旋轉。

  節點旋轉分為左旋轉和又旋轉:

  • 左旋轉:以旋轉結點作為支點,旋轉節點的右子結點變為旋轉結點的父結點,右子結點的左子結點變為旋轉結點的右子結點,旋轉節點的左子結點保持不變。如圖4:

  • 右旋轉:以旋轉結點作為支點,旋轉結點的左子結點變為旋轉結點的父結點,左子結點的右子結點變為旋轉結點的左子結點,旋轉結點的右子結點保持不變。如圖5:

  大家可以看一下,樹被旋轉前后的變化。可以發現其依然能夠維持是一棵二叉查找樹的結構,並且又能夠調節部分節點的結構和左右子樹的高度。所以通過旋轉節點和改變節點的顏色,就能夠將一棵不平衡的紅黑樹(不滿足紅黑樹5個特性的樹)調整成為一棵標准的紅黑樹。下面就開始分析在插入和刪除節點之后,如何讓破壞了平衡性的紅黑樹進行自平橫。

4.2、插入操作

  前面講解紅黑樹特性的地方已經說道紅黑樹也是一棵二叉查找樹,只不過其在插入節點之后需要做自平橫操作(節點旋轉或者顏色改變)。所以由此可知,在插入結點的時候紅黑樹和二叉查的操作是一樣的,也就是先找到節點應該插入到什么位置(父節點的位置),如果在查找遍歷過程中發現值已存在就做修改,如果不存在就插入。節點插入之后可能就會破壞樹的平衡(不符合紅黑樹5大特性),下面我們就做自平橫操作的分析。

  首先,大家要記住一點,在你准備插入一個節點之前,紅黑樹一定是滿足上面那5大特性的,在往樹中插入了一個節點之后就有可能不滿足了。

  初始節點默認設置為紅色節點,為了后面方便描述,在這里我們統一稱插入節點為“當前節點”。在一棵標准的紅黑樹中插入一個節點之后,則會產生如下4個需要自平橫的場景:

  • 場景1:當前結點的父節點為空節點(父節點為根節點)

  平衡操作為,直接將當前節點顏色設置為黑色,然后結束平衡操作。

  • 場景2:當前節點的父節點為黑色節點

  由於前節點為插入節點且其父節點是黑色節點,所以其左右子節點都為葉子節點,其兄弟節點既可以是紅色節點也可以是葉子節點。如下圖:

  注:其中下划線標注的節點為當前節點。

  由於向黑色節點下插入一個紅色子節點不會破壞紅黑樹的平衡,所以可以直接結束平衡操作。

注:在后面的【場景4】中,當前節點不是插入節點時,還會衍生出另一種情況。當前節點的左右子節點為黑色節點,兄弟節點既可以是紅色節點也可以是黑色節。但不論兄弟節點為什么節點,這兩種情況都可以適配成【場景2】。

  • 場景3:當前結點的父節點是紅色節點,並且其叔叔節點不是紅色節點

  由於當前節點的父節點是紅色節點,所以其祖父節點為黑色節點。由於當前節點為插入結點且叔叔節點不為紅色節點,所以其左右子節點、叔叔節點等都是葉子節點。如下圖:

  注:其中下划線標注的節點為當前節點。

  我們以上圖中第一顆樹為例,進行平衡操作。首先將當前節點父節點的顏色置黑並且將祖父節點的顏色置紅,然后以祖父節點為支點進行右旋轉,再然后結束平衡操作。如下圖:

 

  我們再來看上圖中第二棵樹的平衡操作。首先將當前節點的父節點設為當前節點,然后以當前節點為支點進行左旋轉。如下圖:

  那么是不是就變成了和第一棵樹一樣的結構了,其他的就不用再多說了吧?

注:在后面的【場景4】中,當前節點不是插入節點時,還會衍生出另一種情況。當前節點的左右子節點、兄弟節點都為黑色節點。但不論如何,這兩種情況都可以適配成【場景3】。

  • 場景4:當前節點的父節點是紅色節點,並且其叔叔節點也是紅色節點

  由於當前節點的父節點為紅色節點,所以其祖父節點為黑色節點。由於當前節點是插入節點且叔叔節點為紅色節點,所以其左右子節點、兄弟節點、叔叔節點的左右子節點都為葉子節點。如下圖:

  注:其中下划線標注的節點為當前節點

  我們以上圖中第一樹為例,進行平衡操作。首先將當前節點的父節點和叔叔節點顏色置黑,然后將祖父節點的顏色置紅,再然后將祖父節點設置為當前節點。如下圖:

注:在下面的衍生場景中,當前節點不是插入節點時,還會衍生出另一種情況。當前節點的左右子節點、兄弟節點、叔叔節點的左右子節點都為黑色節點。但是不論如何,這兩種情況都可以適配成【場景4】。

  大家不要以為這樣就結束了,我們還得繼續看當前節點的父節點的類型。由此又可以衍生出下面4中情況:

  • 當前節點的祖父節點為空節點,可適配【場景1】
  • 當前節點的父節點為黑色節點,可適配【場景2】。如下圖:

  注:其中下划線標注的節點為當前節點。

  • 當前節點的父節點是紅色節點,並且叔叔節點是黑色節點,可適配【場景3】。如下圖:

  注:其中下划線標注的節點為當前節點。

  • 當前節點的父節點和叔叔節點都為紅色節點,並且其兄弟節點和叔叔節點的左右子節點都為黑色節點,可適配【場景4】。如下圖:

  注:其中下划線標注的節點為當前節點。

  插入總結:平衡的過程是一個循環,如果在當前節點和其父節點上處理過后還無法使樹平衡,就需要將祖父節點設為當前節點並進入下一個循環處理。我們看【場景1】中當前節點的父節點為根節點、【場景2】中當前節點的父節點為黑色節點,都不需要再旋轉或者改變節點顏色,樹已經是平衡狀態了。而【場景3】、【場景4】中經過多次循環旋轉和變色處理將樹平衡過后,我們發現當前節點的父節點要么是根節點要么就是黑色節點。所以我們總結,當父節點為根節點或者父節點為黑色節點時,樹就已經平衡了。所以大家就明白為什么初始插入幾節點默認是紅色的了吧。

  可能大家單看上面的場景分析還是不甚明白。但是沒關系,說了這么多,最終的目的是要在代碼層面去實現的,大家結合上面的場景分析然后去閱讀后面的代碼示例才是最終目的。

4.3、刪除操作

  相對於上面的插入操作,紅黑樹的刪除操作相對來說會復雜一些,其實也就是需要自平橫操作的場景會稍多一些。和插入一樣,紅黑樹的節點刪除操作也是和二叉查找樹一樣。首先要找到需要刪除的節點,如果找不到就結束,如果找到了就看子節點情況。如果有兩個非葉子節點的子節點,則使用前/后繼結點替換刪除節點。如果只有一個非葉子結點的子節點(該子節點下的兩個子節點一定都是葉子節點),則使用該子節點作為前/后繼結點替換刪除節點。那么最終刪除節點就只有兩個葉子結點。

  這時要注意,我們還不能直接就把需要刪除的節點移出,因為刪除節點需要參與到刪除之前的平衡操作,平衡之后才能將節點刪除。這里要說明一點,刪除操作之前進行自平橫平的目的,是要讓刪除節點變成樹中多余的節點。也就是說平衡之后,剔除了刪除節點的紅黑樹還能保持平衡,反之如果沒有剔除樹就不平衡了。

  為了后面方便描述,在這里我們統一稱刪除節點為“當前節點”,在刪除節點之前我們有4個需要平衡的場景:

  • 場景1:當前節點的父節點為空節點(當前節點為根節點)

  直接結束平衡操作,然后將需要刪除的節點刪除。

  • 場景2:當前節點是紅色節點

  由於當前節點是紅色節點,所以當前節點的父節點為黑色節點。由於當前節點為刪除節點,所以其左右子節點為葉子節點,其兄弟節點既可以是紅色節點也可以是葉子節點。如下圖:

  注:其中下划線標注的節點為當前節點。

  我們以上圖中第一顆樹為例,進行平衡操作。在這里當前節點為紅色節點,可以直接將當前節點顏色置黑,然后結束平衡操作,最后將需要刪除的節點刪除即可。如下圖:

  可能有人覺得,既然將當前節點刪除不會破壞樹的平衡,那么直接做刪除操作不就行了,將當前節點的顏色置黑是不是就是多余的操作呢?在這里這一步操作並不多余,而且很重要,大家可以繼續往后面看。

注:在后面的【場景5】中,當前節點不是刪除節點時,還會衍生出另一種情況。當前節點的左右子節點為黑色節點,其兄弟節點既可以是紅色節點也可以是黑色節點。但不論如何,這兩種情況都可以適配成【場景2】。

  • 場景3:當前節點和其兄弟節點都是黑色節點,並且其兄弟節點至少有一個紅色子節點

  由於當前節點和其兄弟節點都是黑色節點,所以當前節點的父節點既可以是紅色節點也可以是黑色節點。由於前節點為刪除節點並且其兄弟節點至少有一個紅色節點,所以其左右子節點為葉子節點,其兄弟節點要么有一個紅色子節點和一個葉子節點,要么有兩個紅色子節點。如下圖:

  注:其中下划線標注的節點為當前節點。

  我們以上圖中第一顆樹為例,進行平衡操作。首先將兄弟節點顏色變為父節點的顏色,然后將父節點和兄弟節點的右子節點的顏色置黑,再然后以父節點為支點進行左旋轉。如下圖:

  我們發現樹已經是平衡的,這里做完操作之后就可以結束平衡,然后將需要刪除的節點刪除就可以了。

  我們再來看上圖中第二棵樹的平衡操作。首先將兄弟節點顏色置紅,並且將兄弟節點的左子節點顏色置黑,然后以兄弟節點為支點進行右旋轉。如下圖:

  那么是不是就變成了和第一棵樹一樣的結構了,其他的就不用再多說了吧?

注:在后面的【場景5】中,當前節點不是刪除節點時,還會衍生出另一種情況。當前節點的左右子節點不為葉子節點,其兄弟節點要么有一個紅色節點一個黑色節點,要么有兩個紅色節點。但不論如何,這兩種情況都可以適配成【場景3】。

  • 場景4:當前節點為黑色節點,其兄弟節點為紅色節點

  由於當前節點的兄弟節點為紅色節點,所以其父節點為黑色節點,其兄弟節點的左右子節點為黑色節點。由於當前節點為刪節點並且是黑色節點,所以其左右子節點為葉子節點。如下圖:

  注:其中下划線標注的節點為當前節點。

  我們以上圖中第一顆樹為例,進行平衡操作。首先將兄弟節點顏色置黑,同時將父節點顏色置紅,然后以父節點為支點進行左旋轉。如下圖:

  大家看上圖,這么處理之后其實樹還沒有平衡,需要刪除的節點還不能刪除。但是當前場景處理到這里就已經結束了,剩下的平衡工作需要交給其他場景進行處理:

  1.如果節點“60”下的兩個子節點中至少有一個紅色節點,則使用【場景3】的平衡處理方式

  2.如果節點“60”下的兩個子節點都為葉子節點,則使用【場景5】的平衡方式處理

注:在后面的【場景5】中,當前節點不是刪除節點時,還會衍生出另一種情況。當前節點的左右子節點不為葉子節點,節點“60”下有兩個黑色子節點,這種情況可以適配上【場景5】中衍生出來的場景。

  • 場景5:當前節點和其兄弟節點都為黑色節點,並且其兄弟節點沒有紅色子節點

  由於當前節點和其兄弟節點都是黑色節點,所以當前節點的父節點既可以是紅色節點也可以是黑色節點。由於前節點為刪除節點且其兄弟節點沒有子節點,所以當前節點和其兄弟節點的左右子節點都為葉子節點。如下圖:

  注:其中下划線標注的節點為當前節點。

  我們以上圖中第一顆樹為例,進行平衡操作。首先將兄弟節點顏色置紅,然后將父節點設為當前節點。如下圖:

注:在下面的衍生場景中,當前節點不是插入節點時,還會衍生出另一種情況。當前節點的左右子節點不為葉子節點,兄弟節點的左右子節點都為黑色節點。但是不論如何,這兩種情況都可以適配成【場景5】。

  大家不要以為這樣就結束了,我們還得繼續看當前節點的兄弟節點的類型。由此又可以衍生出下面5中情況:

  • 當前節點的父節點為空節點,可適配【場景1】
  • 當前是紅色節點,可以適配【場景2】。如下圖:

  注:其中下划線標注的節點為當前節點

  • 當前節點和其兄弟節點都是黑色節點,並且其兄弟節點有一個紅色子節點,可適配【場景3】。如下圖:

  注:其中下划線標注的節點為當前節點

  • 當前節點為黑色節點,其兄弟節點為紅色節點,可適配【場景4】。如下圖:

  注:其中下划線標注的節點為當前節點

  • 當前節點和其兄弟節點都為黑色節點,並且其兄弟節點有兩個黑色子節點,可適配【場景5】。如下圖:

  注:其中下划線標注的節點為當前節點

  刪除總結:和插入平衡的過程一樣,刪除平衡也是一個循環處理的過程。如果在當前節點和其兄弟節點上處理過后還無法使樹平衡,就需要將其父節點設為當前節點並進入下一個循環處理。再看【場景1】中當前節點為根節點、【場景2】中當前節點為紅色節點、【場景3】的平衡處理之后,都不需要再次旋轉或者改變節點顏色,樹已經是平衡狀態了。而【場景4】、【場景5】中經過多次循環旋轉和變色處理將樹平衡之后,發現當前節點要么是根節點要么就是紅色節點,或者是【場景3】處理之后的結果。所以總結,當前節點為根節點或者為黑色節點再或者是【場景3】處理之后的結果時,樹就已經平衡了。所以大家就明白【場景2】中為什么要將當前節點設為黑色節點了吧。

  如果大家單看上面還是很暈乎,沒關系,我們接下來就直接上代碼了,大家對着上面的插入和刪除場景去理解代碼可能就會清晰一些。

4.4、代碼示例

  • java代碼示例:
public class RBTree<K extends Comparable<K>, V> {
    
    private final static int RED = 0, BLACK = 1;
    
    private Node root;
    
    private int size;
    
    /*獲取根節點的key*/
    public Entry<K, V> getRoot() {
        return null == root ? null : new Entry<>(root.key, root.value);
    }
    
    /*獲取樹中節點個數*/
    public int size() {
        return size;
    }
    
    /*判斷以給定節點為根節點的樹是否為一顆紅黑樹*/
    public boolean isRBTree(Node node) {
        // 如果當前節點為空,則從根節點開始遞歸校驗
        node = null == node ? root : node;
        if (null == node) { // 空樹也是一棵紅黑樹
            return true;
        }
        // 獲取當前節點的父節點、左子節點、右子節點
        Node parent = node.parent, nodeLeft = node.left, nodeRight = node.right;
        if (null != parent && parent.left != node && parent.right != node) {
            // 父節不為空,但是父節點的左右子節點引用都沒有指向當前節點,所以不是一顆二叉樹
            return false;
        }
        if (null != nodeLeft) { // 當前節點的左子節點不為空
            if (nodeLeft.parent != node || nodeLeft.compareTo(node) >= 0) {
                // 左子節點父節點引用沒有指向當前節點,不滿足是一顆二叉樹。
                // 或者左子節點的key值大於等於當前節點的key值,不滿足是一顆二叉查找樹(當前節點的key值大於左子節點小於右子節點)
                return false;
            }
            if (RED == nodeLeft.color) {
                if (RED == node.color) {
                    // 當前節點和當前節點的左子節點都紅色節點,不滿足是一顆紅黑樹(紅黑樹中的任何路徑上都不能出現連續的兩個紅色節點)
                    return false;
                }
            } else {
                if (null == nodeRight) {
                    // 左子節點為黑色節點右子節點為空節點,不滿足是一個紅黑樹(從一個節點到它所能到達的任何葉子結點的任何路徑上黑色結點個數必須相等)
                    return false;
                } else if (RED == nodeRight.color && (null == nodeRight.left || null == nodeRight.right)) {
                    // 左子節點為黑色,右子節點為紅色並且其子節點有一個為Nil節點,不滿足是一顆紅黑樹(從一個節點到它所能到達的任何葉子結點的任何路徑上黑色結點個數必須相等)
                    return false;
                }
            }
            if (!isRBTree(nodeLeft)) { // 遞歸左子節點,判斷子樹是否滿足紅黑樹特性
                return false;
            }
        }
        if (null != nodeRight) { // 當前節點的右子節點不為空
            if (nodeRight.parent != node || nodeRight.compareTo(node) <= 0) {
                return false;
            }
            if (RED == nodeRight.color) {
                if (RED == node.color) {
                    return false;
                }
            } else {
                if (null == nodeLeft) {
                    return false;
                } else if (RED == nodeLeft.color && (null == nodeLeft.left || null == nodeLeft.right)) {
                    return false;
                }
            }
            if (!isRBTree(nodeRight)) {
                return false;
            }
        }
        return true;
    }
    
    /*根據key查找value*/
    public V find(K key) {
        if (null == key) {
            throw new NullPointerException();
        }
        Node node = findNode(key, root);
        return null == node ? null : node.value;
    }
    
    /*查找Key值最小的節點*/
    public Entry<K, V> findMin() {
        Node node = root;
        while (null != node && null != node.left) {
            node = node.left;
        }
        return null == node ? null : new Entry<>(node.key, node.value);
    }
    
    /*查找Key值最大的節點*/
    public Entry<K, V> findMax() {
        Node node = root;
        while (null != node && null != node.right) {
            node = node.right;
        }
        return null == node ? null : new Entry<>(node.key, node.value);
    }
    
    /*向紅黑樹中插入節點*/
    public V put(K key, V value) {
        if (null == key) {
            throw new NullPointerException();
        }
        Node current = root, parent = null;
        // 獲取節點要插入的位置的父節點
        int compare = 0;
        while (null != current && 0 != (compare = key.compareTo(current.key))) {
            current = compare > 0 ? (parent = current).right : (parent = current).left;
        }
        if (null != current) { // 要插入的key已存在
            V ov = current.value;
            current.value = value;
            return ov;
        }
        if (null == parent) { // 要插入的樹為空樹
            root = new Node(key, value, BLACK, null);
        } else { // 插入新節點
            Node insert = new Node(key, value, RED, parent);
            current = compare < 0 ? parent.left = insert : (parent.right = insert);
            fixAfterPut(current); // 重新平衡插入節點后的樹
        }
        ++size;
        return null;
    }
    
    /*從紅黑樹中刪除節點*/
    public V remove(K key) {
        if (null == key) {
            throw new NullPointerException();
        }
        Node remove, parent, replace;
        if (null == (remove = findNode(key, root))) { // 需要刪除的節點在樹中找不到
            return null;
        }
        V value = remove.value;
        if (null != remove.left && null != (replace = remove.right)) {
            // 刪除節點的左右子節點都不為空節點,將刪除節點和后繼節點替換
            while (null != replace.left) {
                replace = replace.left;
            }
            remove.key = replace.key;
            remove.value = replace.value;
            remove = replace;
        }
        // 此時子節點最多只有一個非葉子節點
        if (null != (null == (replace = remove.left) ? replace = remove.right : replace)) {
            // 刪除節點的左右子節點有一個不為空,將刪除節點和子節點替換
            remove.key = replace.key;
            remove.value = replace.value;
            remove = replace;
        }
        // 此時子節點全部為葉子節點
        if (null == (parent = remove.parent)) { // 刪除節點為根節點
            root = null;
        } else {
            fixBeforeRemove(remove); // 刪除節點之前需要重新將樹平衡
            remove.parent = parent.right == remove ? parent.right = null : (parent.left = null); // 最后刪除節點
        }
        --size;
        return value;
    }
    
    /*采用中序遍歷二叉樹,保證數據從小到大遍歷*/
    public void forEach(Consumer<Entry<K, V>> action) {
        Deque<Node> deque = new LinkedList<>();
        for (Node node = root; node != null || !deque.isEmpty(); ) {
            for (; node != null; node = node.left) {
                deque.push(node);
            }
            Node pop = deque.pop();
            action.accept(new Entry<>(pop.key, pop.value));
            node = pop.right;
        }
    }
    
    /*查找節點*/
    private Node findNode(K key, Node current) {
        Node found = null;
        for (int compare; null != current && null == found; ) {
            if ((compare = key.compareTo(current.key)) > 0) {
                current = current.right;
            } else if (compare < 0) {
                current = current.left;
            } else {
                found = current;
            }
        }
        return found;
    }
    
    /*左旋轉節點*/
    private void rotateLeft(Node rotate) {
        // 獲取旋轉節點的右子節點
        Node right, parent, broLeft;
        if (null == rotate || null == (right = rotate.right)) {
            return;
        }
        if (null != (broLeft = rotate.right = right.left)) {
            // 將旋轉節點的右子節點設置為右子節點的左子節點,並將右子節點的左子節點父節點設置為旋轉節點
            broLeft.parent = rotate;
        }
        if (null == (parent = right.parent = rotate.parent)) {
            // 右子節點的父節點設置為旋轉節點的父節點,如果父節點為空則將右子節點設置為根節點,並將顏色設置為黑色
            (this.root = right).color = BLACK;
        } else if (parent.left == rotate) {
            parent.left = right;
        } else {
            parent.right = right;
        }
        right.left = rotate;
        rotate.parent = right;
    }
    
    /*右旋轉節點*/
    private void rotateRight(Node rotate) {
        // 獲取旋轉節點的左子節點
        Node left, parent, broRight;
        if (null == rotate || null == (left = rotate.left)) {
            return;
        }
        if (null != (broRight = rotate.left = left.right)) {
            // 將旋轉節點的左子節點設置為左子節點的右子節點,並將左子節點的右子節點父節點設置為旋轉節點
            broRight.parent = rotate;
        }
        if (null == (parent = left.parent = rotate.parent)) {
            // 將左子節點的父節點設置為旋轉節點的父節點,如果父節點為空則將左子節點設置為根節點,並將顏色置黑
            (this.root = left).color = BLACK;
        } else if (parent.left == rotate) {
            parent.left = left;
        } else {
            parent.right = left;
        }
        left.right = rotate;
        rotate.parent = left;
    }
    
    /*插入數據之后將樹進行平衡*/
    private void fixAfterPut(Node current) {
        for (Node parent, grandfather, graLeft, graRight; ; ) {
            if (null == (parent = current.parent)) {
                // TODO: 當前節點父節點是空節點,適配【場景1】
                current.color = BLACK;
                break;
            }
            if (BLACK == parent.color || null == (grandfather = parent.parent)) {
                // TODO: 當前節點的父節點是黑色節點,或者祖父節點是空節點(父節點是根節點),適配【場景2】
                break;
            }
            if ((graLeft = grandfather.left) == parent) { // 父節點為祖父節點的左子節點
                /*
                 * 節點情況分析:
                 * 1、當前節點不為空,並且為紅色節點
                 * 2、當前節點的父節點不為空,並且為紅色節點
                 * 3、當前節點的祖父節點不為空,並且為黑色節點
                 */
                if (null != (graRight = grandfather.right) && RED == graRight.color) {
                    // TODO: 當前節點的叔叔節點是紅色節點,適配【場景4】
                    graRight.color = BLACK; // 將叔叔節點顏色置黑
                    parent.color = BLACK; // 將父節點顏色置黑
                    grandfather.color = RED; // 將祖父節點顏色置紅
                    current = grandfather; // 將祖父節點設為當前節點
                } else {
                    // TODO: 當前節點的叔叔節點是葉子節點或者黑色節點,適配【場景3】
                    if (current == parent.right) {
                        // 當前節點為父節點的右子節點
                        rotateLeft(current = parent); // 將將父節點設為當前節點並將當前節點左旋轉
                        grandfather = (parent = current.parent).parent; // 重新為父節點和祖父節點賦值
                    }
                    parent.color = BLACK; // 將父節點顏色置黑
                    grandfather.color = RED; // 將祖父節點顏色置紅
                    rotateRight(grandfather); // 將祖父節點進行右旋轉
                }
            } else { // 父節點為祖父節點的右子節點,這里就不做注釋了
                if (null != graLeft && RED == graLeft.color) {
                    graLeft.color = BLACK;
                    parent.color = BLACK;
                    grandfather.color = RED;
                    current = grandfather;
                } else {
                    if (current == parent.left) {
                        rotateRight(current = parent);
                        grandfather = (parent = current.parent).parent;
                    }
                    parent.color = BLACK;
                    grandfather.color = RED;
                    rotateLeft(grandfather);
                }
            }
        }
    }
    
    /*刪除節點之前將數平衡*/
    private void fixBeforeRemove(Node current) {
        for (Node parent, left, right; null != current // 當前節點不為空
                && null != (parent = current.parent); ) {  // TODO: 當前節點的父節點是空節點,適配【場景1】
            if (RED == current.color) { // TODO: 當前節點為紅色節點,適配【場景2】
                current.color = BLACK;
                break;
            }
            if ((left = parent.left) == current) { // 如果當前節點為父節點的左子節點
                /*
                 * 節點情況分析:
                 * 1、當前節點是黑色節點
                 * 2、當前節點的兄弟節點不是葉子結點
                 */
                if (RED == (right = parent.right).color) { // TODO: 當前節點的兄弟節點為紅色節點,適配【場景4】
                    /*
                     * 節點情況分析:
                     * 1、父節點為黑色節點;
                     * 2、兄弟節點的左右子節點為黑色節點;
                     */
                    right.color = BLACK; // 將兄弟節點顏色置黑
                    parent.color = RED; // 將父節點顏色置紅
                    rotateLeft(parent); // 將父節點左旋轉(當前節點任然是父節點的左子節點)
                    right = parent.right; // 重新獲取當前節點的兄弟節點
                }
                /*
                 * 節點情況分析:
                 * 1、當前節點的兄弟節點一定為黑色節點
                 */
                Node broLeft = right.left, broRight = right.right;
                if ((null == broRight || BLACK == broRight.color) && (null == broLeft || BLACK == broLeft.color)) {
                    // TODO: 當前節點兄弟節點的左右子節點不存在紅色節點,適配【場景5】
                    /*
                     * 節點情況分析:
                     * 情況1:當前節點兄弟節點的左右子節點都為黑色節點
                     * 情況2:當前節點兄弟節點的左右子節點都為葉子節點
                     */
                    right.color = RED; // 將兄弟節點顏色置紅
                    current = parent; // 將父節點設為當前節點
                } else { // TODO: 當前節點的兄弟節點至少有一個紅色子節點,適配【場景3】
                    if (null == broRight || BLACK == broRight.color) {
                        // 兄弟節點的右子節點為葉子節點或者黑色節點,則兄弟節點的左子節點一定為紅色節點
                        broLeft.color = BLACK; // 將兄弟節點的左子節點顏色置黑
                        right.color = RED; // 將兄弟節點顏色置紅
                        rotateRight(right); // 將兄弟節點右旋轉
                        right = parent.right; // 重新獲取右子節點
                        broRight = right.right;
                    }
                    right.color = parent.color; // 將兄弟節點的顏色置為父節點的顏色
                    broRight.color = BLACK; // 將兄弟節點的右子節點顏色置黑
                    parent.color = BLACK; // 將父節點顏色置黑
                    rotateLeft(parent); // 將父節點左旋轉
                    break;
                }
            } else { // 當前節點為右子節點,這里就不做注釋了
                if (RED == left.color) {
                    left.color = BLACK;
                    parent.color = RED;
                    rotateRight(parent);
                    left = parent.left;
                }
                Node broLeft = left.left, broRight = left.right;
                if ((null == broLeft || BLACK == broLeft.color) && (null == broRight || BLACK == broRight.color)) {
                    left.color = RED;
                    current = parent;
                } else {
                    if (null == broLeft || BLACK == broLeft.color) {
                        broRight.color = BLACK;
                        left.color = RED;
                        rotateLeft(left);
                        left = parent.left;
                        broLeft = left.left;
                    }
                    left.color = parent.color;
                    broLeft.color = BLACK;
                    parent.color = BLACK;
                    rotateRight(parent);
                    break;
                }
            }
        }
    }
    
    class Node implements Comparable<Node> {
        K key;
        V value;
        int color;
        Node parent, left, right;
        
        public Node(K key, V value, int color, Node parent) {
            this.key = key;
            this.value = value;
            this.color = color;
            this.parent = parent;
        }
        
        @Override
        public int compareTo(Node node) {
            return key.compareTo(node.key);
        }
    }
    
    public static class Entry<K, V> {
        private final K key;
        private final V value;
        
        public Entry(K key, V value) {
            this.key = key;
            this.value = value;
        }
        
        public K getKey() {
            return key;
        }
        
        public V getValue() {
            return value;
        }
        
        @Override
        public String toString() {
            return "Entry{" +
                    "key=" + key +
                    ", value=" + value +
                    '}';
        }
    }
    
}
  • java代碼測試用例:
public class RBTreeTest {
    
    @Test
    public void put() {
        ThreadLocalRandom random = ThreadLocalRandom.current();
        RBTree<Integer, String> tree = new RBTree<>();
        for (int i = 0; i < 100; ++i) {
            int key = (random.nextInt(100000) & Integer.MAX_VALUE) % 100;
            tree.put(key, String.valueOf(key));
        }
        RBTree.Entry<Integer, String> root = tree.getRoot();
        System.out.printf("rootKey=%d, rootValue=%s, size=%d, isRBTree=%b\n", root.getKey(), root.getValue(), tree.size(), tree.isRBTree(null));
    }
    
    @Test
    public void remove() {
        ThreadLocalRandom random = ThreadLocalRandom.current();
        RBTree<Integer, String> tree = new RBTree<>();
        for (int i = 0; i < 100; ++i) {
            int key = (random.nextInt(100000) & Integer.MAX_VALUE) % 100;
            tree.put(key, String.valueOf(key));
        }
        //
        while (tree.size() > 0) {
            tree.remove(tree.getRoot().getKey());
            System.out.printf("size=%d, isRBTree=%b\n", tree.size(), tree.isRBTree(null));
        }
    }
    
    @Test
    public void find() {
        ThreadLocalRandom random = ThreadLocalRandom.current();
        RBTree<Integer, String> tree = new RBTree<>();
        for (int i = 0; i < 100; ++i) {
            int key = (random.nextInt(100000) & Integer.MAX_VALUE) % 100;
            tree.put(key, String.valueOf(key));
        }
        System.out.println(tree.find(random.nextInt(100000) % 100));
    }
    
    @Test
    public void findMin() {
        ThreadLocalRandom random = ThreadLocalRandom.current();
        RBTree<Integer, String> tree = new RBTree<>();
        for (int i = 0; i < 100; ++i) {
            int key = (random.nextInt(100000) & Integer.MAX_VALUE) % 100;
            tree.put(key, String.valueOf(key));
        }
        System.out.println(tree.findMin());
    }
    
    @Test
    public void findMax() {
        ThreadLocalRandom random = ThreadLocalRandom.current();
        RBTree<Integer, String> tree = new RBTree<>();
        for (int i = 0; i < 100; ++i) {
            int key = (random.nextInt(100000) & Integer.MAX_VALUE) % 100;
            tree.put(key, String.valueOf(key));
        }
        System.out.println(tree.findMax());
    }
    
    @Test
    public void foreach() {
        ThreadLocalRandom random = ThreadLocalRandom.current();
        RBTree<Integer, String> tree = new RBTree<>();
        for (int i = 0; i < 100; ++i) {
            int key = (random.nextInt(100000) & Integer.MAX_VALUE) % 100;
            tree.put(key, String.valueOf(key));
        }
        RBTree.Entry<Integer, String> root = tree.getRoot();
        System.out.printf("rootKey=%d, rootValue=%s, size=%d, isBSTree=%b\n", root.getKey(), root.getValue(), tree.size(), tree.isRBTree(null));
        System.out.println(tree.findMin().getValue() + " " + tree.findMax().getValue());
        tree.forEach(System.out::println);
    }
    
}
  • C代碼示例:

  由於本人是做java開發的,C的語法和編碼風格並不是很標准,所以大家就將就看一下

#define _CRT_SECURE_NO_WARNINGS
#pragma once

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

    /*紅黑樹節點*/
    typedef struct _RBNode
    {
        void *p_key; // key可以是任意類型,key值比較器比較key的大小
        void *p_value; // value也可以是任意類型
        char color; // 0代表紅色,非0代表黑色
        struct _RBNode *p_parent, *p_left, *p_right;
    }RBNode;

    /*紅黑樹結構體*/
    typedef struct _RBTree
    {
        int size; // 節點數量
        RBNode *p_root; // 根節點指針
        // 樹中key值比較器,參數1:須必較的key值指針,參數2:須必交的key值指針,參數3:返回值指針
        int(*p_kcmp)(void *, void *, char *);
    }RBTree;

    /*
     * 創建一棵空的紅黑樹;
     * 參數列表:
     *   p_tree:紅黑樹指針;
     *   Key_Comparator:代表key值比較器;
     * 返回值:
     *   NULL:代表函數執行失敗;!NULL:代表返回的紅黑樹指針;
     */
    RBTree *create_RBTree(int(*p_kcmp)(void *, void *, char *));

    /*
     * 判斷以給定節點為根節點的樹是否為一顆紅黑樹;
     * 參數列表:
     *     p_tree:紅黑樹指針;
     *   p_node:需要判斷的起始節點指針,如果為空則取根節點;
     *   p_res:判斷結果,0:不是一顆紅黑樹;1:是一顆紅黑樹;
     * 返回值:
     *     0:代表函數執行失敗;!0:代表函數執行成功;
     */
    int is_RBTree(RBTree *p_tree, RBNode *p_node, char *p_res);

    /*
     * 在紅黑樹中查找節點
     * 參數列表:
     *   p_key:要查找的節點的key指針;
     *   p_tree:要查找的紅黑樹的的指針;
     *   p_res:查找到的值;
     * 返回值:
     *   0:代表函數執行失敗;!0:代表函數執行成功;
     */
    int find_RBTree(void *p_key, RBTree *p_tree, void **p_res);

    /*
     * 在紅黑樹中查找Key值最小的節點
     * 參數列表:
     *   p_tree:要查找的紅黑樹的的指針;
     *   p_res:查找到的值;
     * 返回值:
     *   0:代表函數執行失敗;!0:代表函數執行成功;
     */
    int findMin_RBTree(RBTree *p_tree, RBNode **p_res);

    /*
     * 在紅黑樹中查找Key值最大的節點
     * 參數列表:
     *   p_tree:要查找的紅黑樹的的指針;
     *   p_res:查找到的值;
     * 返回值:
     *   0:代表函數執行失敗;!0:代表函數執行成功;
     */
    int findMax_RBTree(RBTree *p_tree, RBNode **p_res);

    /*
     * 向紅黑樹中插入節點
     * 參數列表:
     *   p_key:要插入節點的key
     *   p_value:要插入節點的value
     *   p_tree:要插入節點的紅黑樹
     * 返回值:
     *   0:代表函數執行失敗;!0:代表函數執行成功;
     */
    int put_RBTree(void *p_key, void *p_value, RBTree *p_tree);

    /*
     * 從紅黑樹中刪除節點
     * 參數列表:
     *   p_key:要刪除節點的key
     *   p_tree:要刪除節點的紅黑樹
     *   p_res:刪除節點的value值指針,如果傳入的是NULL,函數內部會釋放這部分空間
     * 返回值:
     *   0:代表函數執行失敗;!0:代表函數執行成功;
     */
    int remove_RBTree(void *p_key, RBTree *p_tree, void **p_res);

    /*
     * 釋放紅黑樹
     * 參數列表:
     *     p_tree:需要釋放的紅黑樹
     */
    void free_RBTree(RBTree *p_tree);

#ifdef __cplusplus
}
#endif // __cplusplus
#include "RBTree.h"

int findNode(int(*p_kcmp)(void *, void *, char *), void *p_key, RBNode *p_curr, RBNode **p_res);

void rotateLeft(RBTree *p_tree, RBNode *p_rotate);

void rotateRight(RBTree *p_tree, RBNode *p_rotate);

void fixAfterPut(RBTree *p_tree, RBNode *p_current);

void fixBeforeRemove(RBTree *p_tree, RBNode *p_current);

RBTree *create_RBTree(int(*p_kcmp)(void *, void *, char *))
{
    if (!p_kcmp) // 如果key值比較器函數為空,則不能創建紅黑樹
        return NULL;

    // 為紅黑樹開辟空間並初始化
    RBTree *p_tree = NULL;
    size_t st_size = sizeof(RBTree);
    if (!(p_tree = (RBTree *)malloc(st_size)))
        return NULL;
    memset(p_tree, 0, st_size);
    p_tree->p_kcmp = p_kcmp;

    return p_tree;
}

int is_RBTree(RBTree *p_tree, RBNode *p_node, char *p_res)
{
    if (!p_tree) // 要校驗的紅黑樹不能為空
        return 0;

    // 如果傳入的要交驗的節點為空,則從根節點開始校驗,如果根節點也為空,則返回成功(空樹也是一棵紅黑樹)
    if (!(p_node = !p_node ? p_tree->p_root : p_node))
        return *p_res = 1;

    // 獲取當前節點的父節點、左子節點、右子節點等指針
    RBNode *p_parent = p_node->p_parent, *p_nodeL = p_node->p_left, *p_nodeR = p_node->p_right;

    // 如果父節不為空,但是父節點的左右子節點指針都沒有指向當前節點,所以不是一顆二叉樹
    if (p_parent && p_parent->p_left != p_node && p_parent->p_right != p_node)
        return !(*p_res = 0);

    char compare = 0;
    if (p_nodeL)  // 當前節點的左子節點不為空
    {
        if (!p_tree->p_kcmp(p_nodeL->p_key, p_node->p_key, &compare)) // 比較左子節點的key和右子節點的key的大小
            return 0;

        // 左子節點父節點引用沒有指向當前節點,不滿足是一顆二叉樹。或者左子節點的key值大於等於當前節點的key值,不滿足是一顆二叉查找樹
        if (p_nodeL->p_parent != p_node || compare >= 0)
            return !(*p_res = 0);

        if (!p_nodeL->color)
        {
            if (!p_node->color) // 當前節點和當前節點的左子節點都紅色節點,不滿足是一顆紅黑樹
                return !(*p_res = 0);
        }
        else
        {
            if (!p_nodeR) // 左子節點為黑色節點右子節點為空節點,不滿足是一個紅黑樹
                return !(*p_res = 0);

            // 左子節點為黑色,右子節點為紅色並且其子節點有一個為Nil節點,不滿足是一顆紅黑樹
            if (!p_nodeR->color && (!p_nodeR->p_left || !p_nodeR->p_right))
                return !(*p_res = 0);
        }

        // 遞歸左子節點,判斷子樹是否滿足紅黑樹特性
        if (!is_RBTree(p_tree, p_nodeL, p_res))
            return 0;
        else if (!*p_res)
            return 1;
    }

    if (p_nodeR) { // 當前節點的右子節點不為空
        if (!p_tree->p_kcmp(p_nodeR->p_key, p_node->p_key, &compare))
            return 0;
        if (p_nodeR->p_parent != p_node || compare <= 0)
            return !(*p_res = 0);
        if (!p_nodeR->color)
        {
            if (!p_node->color)
                return !(*p_res = 0);
        }
        else
        {
            if (!p_nodeL)
                return !(*p_res = 0);
            if (!p_nodeL->color && (!p_nodeL->p_left || !p_nodeL->p_right))
                return !(*p_res = 0);
        }
        if (!is_RBTree(p_tree, p_nodeR, p_res))
            return 0;
        else if (!*p_res)
            return 1;
    }

    return *p_res = 1;
}

int find_RBTree(void *p_key, RBTree *p_tree, void **p_res)
{
    if (!p_key || !p_tree || !p_res)
        return 0;
    RBNode *p_node = NULL;
    if (!findNode(p_tree->p_kcmp, p_key, p_tree->p_root, &p_node)) // 判斷查找節點函數是否執行失敗
        return 0;
    *p_res = !p_node ? NULL : p_node->p_value;
    return 1;
}

int findMin_RBTree(RBTree *p_tree, RBNode **p_res)
{
    if (!p_tree || !p_res)
        return 0;
    RBNode *p_node = p_tree->p_root;
    while (p_node && p_node->p_left)
        p_node = p_node->p_left;
    *p_res = p_node;
    return 1;
}

int findMax_RBTree(RBTree *p_tree, RBNode **p_res)
{
    if (!p_tree || !p_res)
        return 0;
    RBNode *p_node = p_tree->p_root;
    while (p_node && p_node->p_right)
        p_node = p_node->p_right;
    *p_res = p_node;
    return 1;
}

int put_RBTree(void *p_key, void *p_value, RBTree *p_tree, void **p_res)
{
    if (!p_key || !p_tree)
        return 0;

    RBNode *p_parent = NULL, *p_curr = p_tree->p_root;

    // 獲取節點要插入的位置的父節點
    char compare = 0;
    while (p_curr)
    {
        if (!p_tree->p_kcmp(p_key, p_curr->p_key, &compare))
            return 0;
        if (!compare)
            break;

        p_parent = p_curr;
        p_curr = compare > 0 ? p_curr->p_right : p_curr->p_left;
    }

    if (p_curr) // 要插入的key已存在
    {
        if (p_curr->p_key && p_curr->p_key != p_key)
            free(p_curr->p_key);
        if (p_res) // 代表返回值的指針不為空,則表示需要將刪除節點的value值傳出去
            *p_res = p_curr->p_value;
        else if (p_curr->p_value && p_curr->p_value != p_value) // 直接將節點的value值釋放
            free(p_curr->p_value);
        p_curr->p_key = p_key;
        p_curr->p_value = p_value;
        return 1;
    }

    // 開辟紅黑樹節點空間並初始化
    RBNode *p_insert = NULL;
    size_t st_size = sizeof(RBNode);
    if (!(p_insert = (RBNode *)malloc(st_size)))
        return 0;
    memset(p_insert, 0, st_size);
    p_insert->p_key = p_key;
    p_insert->p_value = p_value;

    if (!(p_insert->p_parent = p_parent)) // 要插入節點的紅黑樹是空樹
        (p_tree->p_root = p_insert)->color = 1;
    else // 要插入節點的紅黑樹不是空樹,直接將節點插入
    {
        p_curr = compare < 0 ? p_parent->p_left = p_insert : (p_parent->p_right = p_insert);
        fixAfterPut(p_tree, p_curr); // 重新平衡插入節點后的樹
    }

    ++p_tree->size;
    return 1;
}

int remove_RBTree(void *p_key, RBTree *p_tree, void **p_res)
{
    if (!p_key || !p_tree)
        return 0;

    RBNode *p_remove = NULL, *p_parent = NULL, *p_replace = NULL;
    if (!findNode(p_tree->p_kcmp, p_key, p_tree->p_root, &p_remove)) // 判斷查找節點函數是否執行失敗
        return 0;

    if (!p_remove) // 如果沒有查找到要刪除的節點就直接返回
        return 1;
    else if (p_res) // 代表返回值的指針不為空,則表示需要將刪除節點的value值傳出去
        *p_res = p_remove->p_value;
    else if (p_remove->p_value) // 直接釋放刪除節點的value值
        free(p_remove->p_value);
    p_remove->p_value = NULL;

    if (p_remove->p_left && (p_replace = p_remove->p_right))
    { // 刪除節點的左右子節點都不為空節點,將刪除節點和后繼節點替換
        while (p_replace->p_left)
            p_replace = p_replace->p_left;

        void* temp = p_remove->p_key;
        p_remove->p_key = p_replace->p_key;
        p_replace->p_key = temp;

        p_remove->p_value = p_replace->p_value;
        p_replace->p_value = NULL;

        p_remove = p_replace;
    }
    // 此時子節點最多只有一個非葉子節點
    if ((p_replace = !(p_replace = p_remove->p_left) ? p_remove->p_right : p_replace))
    { // 刪除節點的左右子節點有一個不為空,將刪除節點和子節點替換
        void* temp = p_remove->p_key;
        p_remove->p_key = p_replace->p_key;
        p_replace->p_key = temp;

        p_remove->p_value = p_replace->p_value;
        p_replace->p_value = NULL;

        p_remove = p_replace;
    }
    // 此時子節點全部為葉子節點
    if (!(p_parent = p_remove->p_parent)) // 刪除節點為根節點
        p_tree->p_root = NULL;
    else
    {
        fixBeforeRemove(p_tree, p_remove); // 刪除節點之前需要重新將樹平衡
        p_remove->p_parent = p_parent->p_right == p_remove ? p_parent->p_right = NULL : (p_parent->p_left = NULL); // 最后刪除節點
    }
    
    // 釋放被刪除節點的空間
    if (p_remove->p_key)
        free(p_remove->p_key);
    if (p_remove)
        free(p_remove);
    p_remove = p_remove->p_key = NULL;

    --p_tree->size;
    return 1;
}

void free_RBTree(RBTree *p_tree)
{
    if (!p_tree)
        return;

    for (RBNode *p_node = NULL; p_node = p_tree->p_root; )
        remove_RBTree(p_node->p_key, p_tree, NULL);
    
    free(p_tree);
    p_tree = NULL;
}

static int findNode(int(*p_kcmp)(void *, void *, char *), void *p_key, RBNode *p_curr, RBNode **p_res)
{
    if (!p_kcmp || !p_key || !p_res)
        return 0;

    RBNode *p_found = NULL;
    for (char compare = 0; p_curr && !p_found; )
    {
        if (!p_kcmp(p_key, p_curr->p_key, &compare))
            return 0;

        if (compare > 0)
            p_curr = p_curr->p_right;
        else if (compare < 0)
            p_curr = p_curr->p_left;
        else
            *p_res = p_found = p_curr;
    }

    return 1;
}

static void rotateLeft(RBTree *p_tree, RBNode *p_rotate)
{
    RBNode *p_right, *p_parent, *p_broLeft;

    if (!p_tree || !p_rotate || !(p_right = p_rotate->p_right)) // 如果旋轉節點的右子節點為空節點,則不需要旋轉直接返回
        return;

    // 將旋轉節點的右子節點設置為右子節點的左子節點,並將右子節點的左子節點父節點設置為旋轉節點
    if (p_broLeft = p_rotate->p_right = p_right->p_left)
        p_broLeft->p_parent = p_rotate;

    if (!(p_parent = p_right->p_parent = p_rotate->p_parent))
        // 右子節點的父節點設置為旋轉節點的父節點,如果父節點為空則將右子節點設置為根節點,並將顏色設置為黑色
        (p_tree->p_root = p_right)->color = 1;
    else if (p_parent->p_left == p_rotate)
        p_parent->p_left = p_right;
    else
        p_parent->p_right = p_right;

    p_right->p_left = p_rotate;
    p_rotate->p_parent = p_right;
}

static void rotateRight(RBTree *p_tree, RBNode *p_rotate)
{
    RBNode *p_left, *p_parent, *p_broRight;

    if (!p_tree || !p_rotate || !(p_left = p_rotate->p_left))
        return;

    if (p_broRight = p_rotate->p_left = p_left->p_right)
        p_broRight->p_parent = p_rotate;

    if (!(p_parent = p_left->p_parent = p_rotate->p_parent))
        (p_tree->p_root = p_left)->color = 1;
    else if (p_parent->p_left == p_rotate)
        p_parent->p_left = p_left;
    else
        p_parent->p_right = p_left;

    p_left->p_right = p_rotate;
    p_rotate->p_parent = p_left;
}

static void fixAfterPut(RBTree *p_tree, RBNode *p_current)
{
    for (RBNode *p_parent, *p_gparent, *p_graLeft, *p_graRight; ; )
    {
        if (!(p_parent = p_current->p_parent)) // 當前節點父節點是空節點,適配【場景1】
        {
            p_current->color = 1;
            break;
        }

        if (p_parent->color || !(p_gparent = p_parent->p_parent)) // 當前節點的父節點是黑色節點,或者祖父節點是空節點(父節點是根節點),適配【場景2】
            break;

        if ((p_graLeft = p_gparent->p_left) == p_parent) // 父節點為祖父節點的左子節點
        {
            /*
             * 節點情況分析:
             * 1、當前節點不為空,並且為紅色節點
             * 2、當前節點的父節點不為空,並且為紅色節點
             * 3、當前節點的祖父節點不為空,並且為黑色節點
             */
            if ((p_graRight = p_gparent->p_right) && !p_graRight->color) // 當前節點的叔叔節點是紅色節點,適配【場景4】
            {
                p_graRight->color = 1; // 將叔叔節點顏色置黑
                p_parent->color = 1; // 將父節點顏色置黑
                p_gparent->color = 0; // 將祖父節點顏色置紅
                p_current = p_gparent; // 將祖父節點設為當前節點
            }
            else // 當前節點的叔叔節點是葉子節點或者黑色節點,適配【場景3】
            {
                if (p_current == p_parent->p_right) // 當前節點為父節點的右子節點
                {
                    rotateLeft(p_tree, p_current = p_parent); // 將將父節點設為當前節點並將當前節點左旋轉
                    p_gparent = (p_parent = p_current->p_parent)->p_parent; // 重新為父節點和祖父節點賦值
                }
                p_parent->color = 1; // 將父節點顏色置黑
                p_gparent->color = 0; // 將祖父節點顏色置紅
                rotateRight(p_tree, p_gparent); // 將祖父節點進行右旋轉
            }
        }
        else  // 父節點為祖父節點的右子節點
        {
            if (p_graLeft && !p_graLeft->color)
            {
                p_graLeft->color = 1;
                p_parent->color = 1;
                p_gparent->color = 0;
                p_current = p_gparent;
            }
            else
            {
                if (p_current == p_parent->p_left)
                {
                    rotateRight(p_tree, p_current = p_parent);
                    p_gparent = (p_parent = p_current->p_parent)->p_parent;
                }
                p_parent->color = 1;
                p_gparent->color = 0;
                rotateLeft(p_tree, p_gparent);
            }
        }
    }
}

static void fixBeforeRemove(RBTree *p_tree, RBNode *p_current)
{
    for (RBNode *p_parent, *p_left, *p_right; p_current // 當前節點不為空
        && (p_parent = p_current->p_parent); ) // 當前節點的父節點是空節點,適配【場景1】
    {
        if (!p_current->color) // 當前節點為紅色節點,適配【場景2】
        {
            p_current->color = 1;
            break;
        }
        if ((p_left = p_parent->p_left) == p_current) // 如果當前節點為父節點的左子節點
        {
            /*
             * 節點情況分析:
             * 1、當前節點是黑色節點
             * 2、當前節點的兄弟節點不是葉子結點
             */
            if (!(p_right = p_parent->p_right)->color) // 當前節點的兄弟節點為紅色節點,適配【場景4】
            {
                /*
                 * 節點情況分析:
                 * 1、父節點為黑色節點;
                 * 2、兄弟節點的左右子節點為黑色節點;
                 */
                p_right->color = 1; // 將兄弟節點顏色置黑
                p_parent->color = 0; // 將父節點顏色置紅
                rotateLeft(p_tree, p_parent); // 將父節點左旋轉(當前節點仍然是父節點的左子節點)
                p_right = p_parent->p_right; // 重新獲取當前節點的兄弟節點
            }
            /*
             * 節點情況分析:
             * 1、當前節點的兄弟節點一定為黑色節點
             */
            RBNode *p_broLeft = p_right->p_left, *p_broRight = p_right->p_right;
            if ((!p_broRight || p_broRight->color) && (!p_broLeft || p_broLeft->color))
            {
                // 當前節點兄弟節點的左右子節點不存在紅色節點,適配【場景5】
                /*
                 * 節點情況分析:
                 * 情況1:當前節點兄弟節點的左右子節點都為黑色節點
                 * 情況2:當前節點兄弟節點的左右子節點都為葉子節點
                 */
                p_right->color = 0; // 將兄弟節點顏色置紅
                p_current = p_parent; // 將父節點設為當前節點
            }
            else // 當前節點的兄弟節點至少有一個紅色子節點,適配【場景3】
            {
                if (!p_broRight || p_broRight->color)
                {
                    // 兄弟節點的右子節點為葉子節點或者黑色節點,則兄弟節點的左子節點一定為紅色節點
                    p_broLeft->color = 1; // 將兄弟節點的左子節點顏色置黑
                    p_right->color = 0; // 將兄弟節點顏色置紅
                    rotateRight(p_tree, p_right); // 將兄弟節點右旋轉
                    p_right = p_parent->p_right; // 重新獲取右子節點
                    p_broRight = p_right->p_right;
                }
                p_right->color = p_parent->color; // 將兄弟節點的顏色置為父節點的顏色
                p_broRight->color = 1; // 將兄弟節點的右子節點顏色置黑
                p_parent->color = 1; // 將父節點顏色置黑
                rotateLeft(p_tree, p_parent); // 將父節點左旋轉
                break;
            }
        }
        else // 當前節點為右子節點
        {
            if (!p_left->color)
            {
                p_left->color = 1;
                p_parent->color = 0;
                rotateRight(p_tree, p_parent);
                p_left = p_parent->p_left;
            }
            RBNode *p_broLeft = p_left->p_left, *p_broRight = p_left->p_right;
            if ((!p_broLeft || p_broLeft->color) && (!p_broRight || p_broRight->color))
            {
                p_left->color = 0;
                p_current = p_parent;
            }
            else
            {
                if (!p_broLeft || p_broLeft->color)
                {
                    p_broRight->color = 1;
                    p_left->color = 0;
                    rotateLeft(p_tree, p_left);
                    p_left = p_parent->p_left;
                    p_broLeft = p_left->p_left;
                }
                p_left->color = p_parent->color;
                p_broLeft->color = 1;
                p_parent->color = 1;
                rotateRight(p_tree, p_parent);
                break;
            }
        }
    }
}
  • C代碼測試用例:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "RBTree.h"

#define _VALUE_SIZE 20

static int kcmp(void *p_key1, void *p_key2, char *p_res)
{
    int compare = *((int *)p_key1) - *((int *)p_key2);
    if (compare > 0)
        *p_res = 1;
    else if (compare < 0)
        *p_res = -1;
    else
        *p_res = 0;
    return 1;
}

RBTree *init_RBTree(int n_size, int max_key)
{
    RBTree *p_tree = NULL;
    p_tree = create_RBTree(kcmp);
    if (!(p_tree = create_RBTree(kcmp)))
    {
        printf("創建紅黑樹出現錯誤\n");
        return NULL;
    }

    int *p_key = NULL;
    char *p_value = NULL;
    srand((unsigned int)time(NULL));
    for (int i = 0; i < n_size; ++i) {
        if (!(p_key = (int *)malloc(sizeof(int))))
        {
            printf("開辟key空間失敗\n");
            return p_tree;
        }
        if (!(p_value = (char *)malloc(_VALUE_SIZE)))
        {
            if (!p_key)
                free(p_key);
            p_key = NULL;
            printf("開辟value空間失敗\n");
            return p_tree;
        }
        _itoa_s((*p_key = rand() % max_key), p_value, _VALUE_SIZE, 16);
        if (!put_RBTree(p_key, p_value, p_tree))
        {
            printf("向紅黑樹中插入節點失敗\n");
            return p_tree;
        }
    }

    return p_tree;
}

void testFind_RBTree()
{
    RBTree *p_tree = NULL;
    if (!(p_tree = init_RBTree(100, 1000)))
    {
        printf("紅黑樹初始化失敗\n");
        return;
    }
    RBNode *p_root = p_tree->p_root;
    printf("初始化紅黑樹成功: root_key=%d, size=%d\n", *((int *)p_root->p_key), p_tree->size);

    srand((unsigned int)time(NULL));
    int key = rand() % 1000;
    void *p_value = NULL;
    find_RBTree(&key, p_tree, &p_value);
    if (p_value)
        printf("key=%d, value=%s\n", key, (char *)p_value);
    else
        printf("key=%d, value=NULL\n", key);

    free_RBTree(p_tree); // 釋放紅黑樹
}

void testPutAndRemove_RBTree()
{
    RBTree *p_tree = NULL;
    if (!(p_tree = init_RBTree(100, 1000)))
    {
        printf("紅黑樹初始化失敗\n");
        return;
    }
    RBNode *p_root = p_tree->p_root;
    printf("初始化紅黑樹成功: root_key=%d, size=%d\n", *((int *)p_root->p_key), p_tree->size);

    char isRBTree = 0;
    for (int i = 0; i < 20; ++i)
    {
        RBNode *p_node = p_tree->p_root;
        if (p_node)
        {
            printf("刪除[root_key=%d]后,", *((int *)p_node->p_key));
            remove_RBTree(p_node->p_key, p_tree, NULL);
            is_RBTree(p_tree, NULL, &isRBTree);
            printf("p_tree[%s]一顆紅黑樹, size=%d\n", isRBTree ? "還是" : "不是", p_tree->size);
        }
    }
    
    free_RBTree(p_tree); // 釋放紅黑樹
}

int main(void)
{
    testPutAndRemove_RBTree();
    //testFind_RBTree();

    system("pause");
    return EXIT_SUCCESS;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM