title: 紅黑樹
date: 2022-03-31 10:41:30
sidebar: auto
categories:
- 資料結構
- 二元樹
tags:
- 紅黑樹
1.2 樹的特點
Trees | Graphs |
---|---|
1. A tree is a special kind of graph that there are never multiple paths exist. There is always one way to get from A to B. | 1. A graph is a system that has multiple ways to get from any point A to any other point B. |
2. Tree must be connected. | 2. Graph may not be connected. |
3. Since it connected we can reach from one particular node to all other nodes. This kind of searching is called traversal. | 3. Traversal always not applicable on graphs. Because graphs may not be connected. |
4. Tree contains no loops, no circuits. | 4. Graph may contain self-loops, loops. |
5. There must be a root node in tree. | 5. There is no such kind of root node in graphs |
6. We do traversal on trees. That mean from a point we go to each and every node of tree. | 6. We do searching on graphs. That means starting from any node try to find a particular node which we need. |
7. pre-order, in-order, post-order are some kind of traversals in trees. | 7. Breath first search, Depth first search, are some kind of searching algorithms in graphs. |
8. Trees are directed acyclic graphs. | 8. Graphs are cyclic or acyclic. |
9. Tree is a hierarchical model structure. | 9. Graph is network model. |
10. All trees are graphs. | 10. But all graphs are not trees. |
11. Based on different properties trees can be classified as Binary tree, Binary search tree, AVL trees, Heaps. | 11. We differ the graphs like directed graphs and undirected graphs. |
12. If tree have 「n」 vertices then it must have exactly 「n-1」 edges only. | 12. In graphs number of edges doesn’t depend on the number of vertices. |
13. Main use of trees is for sorting and traversing. | 13. Main use of graphs is coloring and job scheduling. |
14. Less in complexity compared to graphs. | 14. High complexity than trees due to loops. |
除最後一層無任何子節點外,每一層上的所有結點都有兩個子結點的二元樹
滿二元樹特點:
除最後一層外,每一層上的結點數均達到最大值;在最後一層上只缺少右邊的若干結點
完全二元樹特點:
堆的實現一般基於完全二元樹
什麼是平衡二叉查詢樹
由前蘇聯的數學家 Adelse-Velskil 和 Landis 在 1962 年提出的高度平衡的二元樹,根據科學家的英文名也稱為 AVL 樹。它具有如下幾個性質:
可以是空樹。
平衡之意,如天平,即兩邊的分量大約相同。
最早的自平衡二元搜尋樹結構
第一個自平衡二叉查詢樹就是AVL 樹,它規定,每個結點的左右子樹的高度之差不超過 1。在插入或刪除結點,打破平衡後,就會通過一次或多次樹旋轉來重新平衡。
為什麼有平衡二叉查詢樹
平衡二叉查詢樹的缺點
AVL 樹是嚴格平衡的,適用於查詢密集型應用程式,因為在頻繁插入或刪除結點的場景下,它花費在樹旋轉的代價太高。
而紅黑樹就是一種折中方案,它不追求完美平衡,只求部分達到平衡,從而降低在調整時樹旋轉次數。
在二元樹的第i層上最多有2 i-1 個節點 。(i>=1)
二元樹中如果深度為k,那麼最多有2k-1個節點。(k>=1)
n0=n2+1 n0表示度數為0的節點 n2表示度數為2的節點
在完全二元樹中,具有n個節點的完全二元樹的深度為[log2n]+1,其中[log2n]+1是向下取整。
若對含 n 個結點的完全二元樹從上到下且從左至右進行 1 至 n 的編號,則對完全二元樹中任意一個編號為 i 的結點:
(1) 若 i=1,則該結點是二元樹的根,無雙親, 否則,編號為 [i/2] 的結點為其雙親結點;
(2) 若 2i>n,則該結點無左孩子, 否則,編號為 2i 的結點為其左孩子結點;
(3) 若 2i+1>n,則該結點無右孩子結點, 否則,編號為2i+1 的結點為其右孩子結點
紅黑樹的英文是「Red-Black Tree",簡稱R-B Tree。
紅黑樹是一種二叉查詢樹,通過設定一些規則來保證二元搜尋樹的平衡性,使二元搜尋樹不會在極端情況下變成連結串列。
紅黑樹也是一種平衡二叉查詢樹的變體,它的左右子樹高差有可能大於1,所以紅黑樹不是嚴格意義上的平衡二元樹(AVL),但對之進行平衡的代價較低, 其平均統計效能要強於 AVL。
為了更好地理解什麼是紅黑樹以及這種看起來特殊的資料結構是如何被提出的,我們首先需要了解一下2-3樹,這種資料結構與紅黑樹是等價的。
說到紅黑樹,就不得不提 2-3 樹,因為,紅黑樹可以說就是它的一種特殊實現,對它有所瞭解,非常有助於理解紅黑樹。
與2-3樹新增元素是等價的
2-3 樹插入的都是葉子結點,紅黑樹插入的結點都是紅色的,因為在 2-3 樹中,待插入結點都認為可以插入到一個多值結點中。
在分析插入和刪除之前,先了解下什麼是樹旋轉。樹旋轉是二元樹中調整子樹的一種操作,常用於調整樹的區域性平衡性,它包含兩種方式,左旋轉和右旋轉。
其實旋轉操作很容易理解:左旋轉就是將用兩個結點中的較小者作為根結點變為將較大者作為根結點,右旋轉剛好於此相反,如上圖所示:
紅黑樹的旋轉其實就是為了確保和其結構相同的 2-3樹的一一對應關係,同時保證紅黑樹的有序性和平衡性。
增加一個元素右傾:進行一次左旋轉
顏色翻轉:
右旋轉:
左傾紅黑樹(Left-leaning Red-Black Tree)是紅黑樹的一種變種,它的主要思想是將紅色節點向左傾斜,從而簡化插入和刪除操作的實現。在左傾紅黑樹中,所有節點都被標記為紅色或黑色,而且滿足以下性質:
下面是左傾紅黑樹的Java程式碼實現:
public class LeftLeaningRedBlackTree<Key extends Comparable<Key>, Value> {
private static final boolean RED = true;
private static final boolean BLACK = false;
private Node root;
private class Node {
private Key key;
private Value val;
private Node left, right;
private boolean color;
public Node(Key key, Value val, boolean color) {
this.key = key;
this.val = val;
this.color = color;
}
}
private boolean isRed(Node x) {
if (x == null) return false;
return x.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;
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;
return x;
}
private void flipColors(Node h) {
h.color = RED;
h.left.color = BLACK;
h.right.color = BLACK;
}
public void put(Key key, Value val) {
root = put(root, key, val);
root.color = BLACK;
}
private Node put(Node h, Key key, Value val) {
if (h == null) return new Node(key, val, RED);
int cmp = key.compareTo(h.key);
if (cmp < 0) h.left = put(h.left, key, val);
else if (cmp > 0) h.right = put(h.right, key, val);
else h.val = val;
// 左旋
if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h);
// 右旋
if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);
// 顏色翻轉
if (isRed(h.left) && isRed(h.right)) flipColors(h);
return h;
}
}
在這個實現中,我們使用了三種基本操作來維護左傾紅黑樹的性質:
在插入節點時,我們首先按照二元搜尋樹的方式找到要插入的位置,並將節點標記為紅色。然後,我們檢查當前節點的父節點、父節點的兄弟節點和祖父節點的顏色,根據需要進行旋轉和顏色翻轉,以保證左傾紅黑樹的性質不被破壞。
在這個實現中,我們將所有空節點都視為黑色節點,並將根節點標記為黑色。這些約定使得插入操作更加簡單,同時保證了左傾紅黑樹的性質。
總的來說,左傾紅黑樹是一種比較優秀的資料結構,它能夠在保持紅黑樹性質的前提下,簡化插入和刪除操作的實現,同時也能夠保持良好的平衡效能。
RB-Tree和AVL樹作為BBST,其實現的演演算法時間複雜度相同,AVL作為最先提出的BBST,貌似RB-tree實現的功能都可以用AVL樹是代替,那麼為什麼還需要引入RB-Tree呢?
AVL更平衡,結構上更加直觀,時間效能針對讀取而言更高;維護稍慢,空間開銷較大。
紅黑樹,讀取略遜於AVL,維護強於AVL,空間開銷與AVL類似,內容極多時略優於AVL,維護優於AVL。
基本上主要的幾種平衡樹看來,紅黑樹有著良好的穩定性和完整的功能,效能表現也很不錯,綜合實力強,在諸如STL的場景中需要穩定表現。
紅黑樹的查詢效能略微遜色於AVL樹,因為其比AVL樹會稍微不平衡最多一層,也就是說紅黑樹的查詢效能只比相同內容的AVL樹最多多一次比較,但是,紅黑樹在插入和刪除上優於AVL樹,AVL樹每次插入刪除會進行大量的平衡度計算,而紅黑樹為了維持紅黑性質所做的紅黑變換和旋轉的開銷,相較於AVL樹為了維持平衡的開銷要小得多
紅黑樹是一種自平衡的二元搜尋樹,它可以在保持二元搜尋樹的性質下,保持樹的高度平衡,從而保證了樹操作的時間複雜度在最壞情況下也是O(log n)。由於其優秀的效能表現,紅黑樹被廣泛應用於Java的TreeMap和TreeSet中。
TreeMap和TreeSet都是基於紅黑樹實現的資料結構,它們分別用於實現基於鍵的有序對映和基於元素的有序集合。它們都支援一系列的操作,如插入、刪除、查詢最大值、查詢最小值、查詢某個鍵或元素的前驅或後繼等操作。
在TreeMap中,每個鍵值對被表示為一個節點,節點按照鍵的大小進行排序,並儲存在一棵紅黑樹中。在TreeSet中,每個元素被表示為一個節點,節點按照元素的大小進行排序,並儲存在一棵紅黑樹中。
由於紅黑樹的自平衡性質,TreeMap和TreeSet能夠在插入、刪除、查詢等操作中保持較為穩定的效能表現,具有很高的效率和可靠性。在Java中,TreeMap和TreeSet被廣泛應用於需要高效實現有序對映和有序集合的場景,如排序、搜尋、去重等場景,是非常常用的資料結構之一。
演演算法導論中的紅黑樹的實現
package rbTree;
import java.util.HashMap;
/**
* @Author WaleGarrett
* @Date 2021/7/11 18:48
*/
public class RBTree<Key extends Comparable<Key>, Value> {
private class Node<Key, value>{
private Key key;
private Value value;
private Node<Key, Value> left;
private Node<Key, Value> right;
private int color;
public Node(Key key, Value value, int color){
this.key = key;
this.value = value;
this.color = color;
}
}
private static final int RED = 0;
private static final int BLACK = 1;
/**
* 判斷一個結點的顏色是否是紅色
* @param node
* @return
*/
private boolean isRed(Node<Key, Value> node){
if(node == null)
return false;
return node.color == RED;
}
/** node x
* 左旋轉: / \ / \
* @param node T1 x ----> node T3
* @return / \ / \
* T2 T3 T1 T2
*/
private Node<Key, Value> leftRotate(Node<Key, Value> node){
Node x = node.right;
node.right = x.left;
x.left = node;
x.color = node.color;
node.color = RED;
return x;
}
/** node x
* 右旋轉: / \ / \
* @param node x T2 ----> T3 node
* @return / \ / \
* T3 T1 T1 T2
*/
private Node<Key, Value> rightRotate(Node<Key, Value> node){
Node x = node.left;
node.left = x.right;
x.right = node;
x.color = node.color;
node.color = RED;
return x;
}
/**
* 顏色翻轉:當左右指標均為紅色時進行顏色翻轉
* @param node
*/
private void flipColor(Node<Key, Value> node){
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}
/**
* 根節點
*/
private Node<Key, Value> root;
/**
* 新增元素
* @param key
* @param value
*/
public void add(Key key, Value value){
root = add(root, key, value);
// 保證根節點的顏色始終是黑色
root.color = BLACK;
}
public Node add(Node<Key, Value> node, Key key, Value value){
/**
* 新加入的結點始終保持是紅色的
*/
if(node == null)
return new Node<Key, Value>(key, value, RED);
int compare = key.compareTo(node.key);
if(compare < 0){
// key小於當前node結點,新結點必然插入到node的左子樹
node.left = add(node.left, key, value);
}else if(compare > 0){
// key大於當前node結點,新結點必然插入到node的右子樹
node.right = add(node.right, key, value);
}else{
node.value = value;
}
/**
* 使紅黑樹樹保持平衡
*/
// 當node結點的右結點為紅色,而左結點為黑色時,左旋
if(isRed(node.right) && !isRed(node.left))
node = leftRotate(node);
// 當node左側有兩個連續的紅色結點時,右旋
if(isRed(node.right) && isRed(node.right.right))
node = rightRotate(node);
// 當node的左右結點均為紅色時,顏色翻轉
if(isRed(node.left) && isRed(node.right))
flipColor(node);
return node;
}
}