查找(二):徹底理解紅黑樹和平衡查找樹


平衡查找樹

在之前的二分搜索和二叉查找樹中已經能夠很好地解決查找的問題了,但是它們在最壞情況下的性能還是很糟糕,我們可以在查找二叉樹中,每次動態插入或刪除某結點時,都重新構造為完全二叉樹,但是這樣代價太大,所以就引出了平衡查找樹。

詳細的數學定義就不給出了,因為既不直觀也記不住,直接給出一個平衡二叉樹的圖:

 

相信這個圖一看就明白了,平衡查找樹(以下簡稱BST或2-3查找樹),下面給出一些基本的定義:

一棵2-3查找樹或為一棵空樹,或由一下結點組成:

2-結點,含有一個鍵(及其對應的值)和兩條鏈接,左鏈接指向的2-3樹中的鍵都小於該結點,右鏈接指向的2-3樹中的鍵都大於該結點。

3-結點,含有兩個鍵(及其對應的值)和三條鏈接,左鏈接指向的2-3樹中的鍵都小於該結點,中鏈接指向的2-3樹中的鍵都位於該結點的兩個鍵之間,右鏈接指向的2-3樹中的鍵都大於該結點。

 

 

 

因為平衡二叉樹在插入和刪除過程中需要判斷插入的結點時2-結點還是3-結點等等一系列問題,實現起來代碼量特別大,並且會增加額外開銷,所以就提出了紅黑樹。

 

紅黑樹

紅黑樹的基本思想是用標准的二叉查找樹(完全由2-結點構成)和一些額外的信息(替換3-結點)來表示2-3樹。

先給圖:

 

由上圖可以很明顯的看到紅黑樹可以完全代替2-3樹,下面給出紅黑樹的定義:

  1. 紅連接均為左鏈接。
  2. 沒有任何一個結點同時和兩條紅鏈接相連
  3. 該樹是完美黑色平衡的,即任意空鏈接到根節點的路徑上的黑鏈接的數量相同

 

 

先給出紅黑樹Node的定義:

  

    private class Node<T> {
        T key;
        Node leftChild = null;
        Node rightChild = null;
        boolean color;
        
        Node(T key,boolean color) {
            this.key = key;
            this.color = color;
        }
    }

 

在Node類中,相比普通的二叉樹,只是多了一個color屬性,因為對於每一個非根節點,有且僅有一個鏈接指向它,所以這個color用來表示指向這個節點的鏈接是紅色還是黑色,也可以理解為此節點是紅色或黑色。

 

然后再給出兩個操作:左旋轉和右旋轉以及顏色轉換。

這三個操作都是局部改變,並不會對整個紅黑樹造成破壞

左旋轉:

對於這個情況,h的右鏈接為紅色,不符合紅黑樹的定義

 

操作的代碼如下:

    public <T> Node<T> rotateLeft(Node<T> h) {
        Node<T> x = h.rightChild;
        h.rightChild = x.leftChild;
        x.leftChild = h;
        x.color = h.color;
        h.color = RED;
        
        return x;
    }

 

同理右旋轉也就是將左邊的紅色鏈接換到右邊:

    public <T> Node<T> rotateRight(Node<T> h) {
        Node<T> x = h.leftChild;
        h.leftChild = x.rightChild;
        x.rightChild = h;
        x.color = h.color;
        h.color = RED;
        
        return x;
    }

 

顏色轉換:

 

 

實現代碼

    public <T> void flipColors(Node<T> h) {
        h.color = RED;
        h.leftChild.color = BLACK;
        h.rightChild.color = BLACK;
    }

 

這三種操作說完了,那么我們為什么需要這三個操作呢? 其實數學原理特別復雜,如果大家有興趣可自行GOOGLE。

 

答案是這三個操作時用來構建一個紅黑樹的,當插入一個元素到紅黑樹中時,需要沿着插入點到根節點的路徑上向上移動,對於經過的每個結點,有如下操作:

  1. 如果右子節點是紅色的而左子節點也是紅色的,進行右旋轉。
  2. 如果左子節點是紅色的且它的左子節點也是紅色的,進行右旋轉
  3. 如果左右子節點均為紅色,進行顏色轉換。

 

 

這樣的話只需要在二叉查找樹上稍作修改即可,完整代碼如下:

public class RedBlackTree extends SearchBase {
    
    private static final boolean RED = true;
    private static final boolean BLACK = false;
    
    @SuppressWarnings("unused")
    private class Node<T> {
        T key;
        Node leftChild = null;
        Node rightChild = null;
        boolean color;
        
        Node(T key,boolean color) {
            this.key = key;
            this.color = color;
        }
    }

    /* (non-Javadoc)
     * @see Search.SearchBase#search(java.lang.Comparable)
     */
    @Override
    public <T> Integer search(Comparable<T> key) {
        // TODO Auto-generated method stub
        return null;
    }
    
    @SuppressWarnings("unchecked")
    public <T> T search(Comparable<T> key,Node<T> root) {
        // TODO Auto-generated method stub
        Node<T> node = root;
        while(node != null) {
            if(key.compareTo((T) node.key) < 0) {
                node = node.leftChild;
            } else if(key.compareTo((T) node.key) > 0){
                node = node.rightChild;
            } else {
                break;
            }
        }
        
        if(node == null)
            return null;
        else 
            return (T) node.key;
    }

    public <T> Node<T> rotateLeft(Node<T> h) {
        Node<T> x = h.rightChild;
        h.rightChild = x.leftChild;
        x.leftChild = h;
        x.color = h.color;
        h.color = RED;
        
        return x;
    }
    
    public <T> Node<T> rotateRight(Node<T> h) {
        Node<T> x = h.leftChild;
        h.leftChild = x.rightChild;
        x.rightChild = h;
        x.color = h.color;
        h.color = RED;
        
        return x;
    }
    
    public <T> void flipColors(Node<T> h) {
        h.color = RED;
        h.leftChild.color = BLACK;
        h.rightChild.color = BLACK;
    }
    
    public <T> boolean isRed(Node<T> node) {
        if(node == null)
            return false;
        return node.color == RED;
    }
    
    @SuppressWarnings("unchecked")
    public <T> Node<T> put(Node<T> node,Comparable<T> key) {
        if(node == null)
            return new Node(key,RED);
        
        if(key.compareTo((T) node.key) < 0)
            node.leftChild = put(node.leftChild,key);
        else if(key.compareTo((T) node.key) > 0)
            node.rightChild = put(node.rightChild,key);
        else 
            node.key = (T) key;
        
        if(isRed(node.rightChild) && !isRed(node.leftChild))
            node = rotateLeft(node);
        if(isRed(node.leftChild) && isRed(node.leftChild.leftChild)) 
            node = rotateRight(node);
        if(isRed(node) && isRed(node.rightChild))
            flipColors(node);
        
        return node;
    }
    
    public <T> void traverseTree(Node<T> node) {
        if(node == null)
            return;
        
        traverseTree(node.leftChild);
        System.out.print(node.key);
        traverseTree(node.rightChild);
    }
    
    public static <T> void main(String[] args) {
        Integer[] b = {1,4,2,6,7,0,3};
        RedBlackTree redBlackTree = new RedBlackTree();
        RedBlackTree.Node<Integer> root = null;
        
        for(int i=0;i<b.length;i++) {
            root = redBlackTree.put(root, b[i]);
        }
        
        redBlackTree.traverseTree(root);
        
        System.out.println();
        Integer key = redBlackTree.search(8, root);
        System.out.println(key);
    }
}

 

紅黑樹平均插入效率:lgN

紅黑樹平均查詢效率:lgN


免責聲明!

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



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