資料結構 — 淺析紅黑樹原理以及實現
淺析紅黑樹原理以及實現
我們在上一篇部落格認識到了平衡二叉樹(AVLTree),瞭解到平衡二叉樹的性質,其實平衡二叉樹最大的作用就是查詢,AVL樹的查詢、插入和刪除在平均和最壞情況下都是O(logn)。AVL樹的效率就是高在這個地方。如果在AVL樹中插入或刪除節點後,使得高度之差大於1。此時,AVL樹的平衡狀態就被破壞,它就不再是一棵二叉樹;為了讓它重新維持在一個平衡狀態,就需要對其進行旋轉處理, 現在呢,我們來思考一下雖然AVL樹的查詢效率高,但是呢構建一顆AVL樹的成本是多少?? 因為當它的高度差大於1的時候,就要觸發旋轉演算法來調節平衡二叉樹,所以當你資料足夠大的時候,那麼你建立一顆平衡二叉樹的成本其實不小. 這個時候就有人開始思考,並且提出了有這張圖我也不需要再解釋什麼了,你的最短路徑就是全黑節點,最長路徑就是一個紅節點一個黑節點,最後黑色節點相同時,最長路
此時對該子樹進行操作,parent節點和uncle節點變為黑,parentparent節點變為紅,這樣我們就保證該子樹中每條路徑中黑色節點相同並且沒有連續的紅節點,然後再讓cur等於parentparent節點繼續往上繼續調整,直到該樹中沒有連續的紅節點. 具體的過程如圖所示:
2. 新插入節點Cur的父親為紅,並且爺爺節點(parentparent)為黑,叔叔節點(uncle)不存在或存在且為黑.
首先我們要明白,第二種情況一定是第一種情況調整後,然後向上調整的時候所遇到的.也就是說你單純插入一個節點是不可能碰到這種情況,出現這種情況的原因就是,第一種情況調整後,它的parentparent節點變為紅色,然後才會出現這種情況. 那麼為什麼呢??因為就拿上面這個子樹來看,如果你cur是新插入的話,明顯就很不合理! 因為你的父親是紅的而你的叔叔是黑的! 這條路徑很明顯黑節點個數和別的路徑不一樣,所以只有一種可能那就是,cur以前是黑節點,因為顏色調整被變成紅色,進而觸發旋轉操作.然後我們對這棵子樹進行操作,這裡有一些旋轉的知識如果不明白的話,可以去看我得上一個部落格:AVL樹. 該部落格裡面對旋轉操作有詳細的講解.好了我們繼續. 拿這個圖為例子. 我們需要根據parentparent節點進行右單旋. 然後將parent節點變為黑色,cur節點和parentparent變為紅色.當然如果parent為parentparent的右,cur為patent的右就是左單旋了. 具體過程如圖所示:
2. 新插入節點Cur的父親為紅,並且爺爺節點(parentparent)為黑,叔叔節點(uncle)不存在或存在且為黑.
這種情況查德一看跟上面一模一樣(其實就是一模一樣(手動微笑)). 但是我在這裡提的是一個特殊一點的情況,因為這裡需要觸發右左雙旋或者左右雙旋. 我們看下圖這種情況,它滿足上述情況但是無法觸發左單旋或者右單旋.所以我們得對它進行一點操作,讓它可以旋轉.
就拿這個圖來說,我們先對parent進行左單選,再然後針對parentpatent進行右單旋. 最後進行於上面相同的變色操作即可. 當然右左雙旋步驟也一樣
程式碼實現:
bool Insert(const K& key, const V& val)
{
//_Insert(_root, x, y);
if (_root == NULL)
{
_root = new Node(key, val);
_root->_col = BLACK;
}
Node* cur = _root;
Node* parent = cur;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key == key)
{
return true;
}
}
if (parent->_key > key)
{
parent->_left = new Node(key, val);
parent->_left->_parent = parent;
cur = parent->_left;
}
else
{
parent->_right = new Node(key, val);
parent->_right->_parent = parent;
cur = parent->_right;
}
//目前父親節點,插入節點,叔叔節點已經就緒.
while(parent && parent->_col == RED)
{
Node* parentparent = parent->_parent;
Node* uncle = NULL;
if (parentparent->_left == parent)
uncle = parentparent->_right;
else
uncle = parentparent->_left;
if(uncle && uncle->_col == RED)
{
parentparent->_col = RED;
parent->_col = BLACK;
uncle->_col = BLACK;
cur = parentparent;
parent = cur->_parent;
}
else if (uncle == NULL || uncle->_col == BLACK)
{
if (parentparent->_left == parent)
{
if (parent->_left == cur)
{
RotateR(parentparent);
parent->_col = BLACK;
}
else
{
RotateLR(parentparent);
cur->_col = BLACK;
}
}
else
{
if (parent->_right == cur)
{
RotateL(parentparent);
parent->_col = BLACK;
}
else
{
RotateRL(parentparent);
cur->_col = BLACK;
}
}
parentparent->_col = RED;
if (parentparent == _root)
{
_root = parent;
}
}
else
{
assert(false);
}
if (_root->_col == RED)
{
_root->_col = BLACK;
}
}
return false;
}
紅黑樹的刪除
紅黑樹的刪除有一定的難度,這裡我就說的沒有這麼詳細了其實它跟搜尋樹的刪除很類似就是尋找一個替換節點然後刪掉它的替換節點
我們要刪除del這個時候,直接刪除del成本太高了,然後我們尋找一個邊緣節點跟他交換,然後再刪除它.所以呢,這樣刪除的成本是
最小的. 刪除分三個情況:
1.del的左為空.
2.del的右為空.
3.del的左右都不為空.
前兩種很容易思考,第三種左右都不為空的情況才需要我們的邊緣替換法.所以我這裡附上搜索二叉樹的刪除的程式碼. 紅黑樹刪除的代
碼暫時我還沒有解決所以這裡之後搜尋二叉樹的刪除程式碼. 我們可以想著思考思考說不定就寫完了.
程式碼實現:
bool Remove(const K& key)
{
if (_root == NULL)
{
return false;
}
Node* cur = _root;
Node* parent = NULL;
while (cur)
{
if (cur->key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->key == key)
{
break;
}
}
Node* del = cur;
if (cur->_left == NULL) //左為空
{
if (parent == NULL)
{
_root = cur->_right;
if (_root)
_root->_parent = NULL;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
if (cur->_right)
cur->_right->_parent = parent;
}
}
else if (cur->_right == NULL)//右為空
{
if (parent == NULL)
{
_root = cur->_left;
if (_root)
_root->_parent = NULL;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
if (cur->_left)
cur->_left->_parent = parent;
}
}
else //左右都不為空
{
if (parent == NULL || cur == parent->_left) //如果cur在左邊.
{
del = cur->_right;
while (del->_left)
{
del = del->_left;
}
del->_parent->_left = del->_right;;
}
else //cur在parent的右邊.
{
del = cur->_left;
while (del->_right)
{
del = del->_right;
}
del->_parent->_right = del->_left;
}
cur->key = del->key;
cur->val = del->val;
}
delete del;
}
編寫一個檢驗該二叉樹是否為紅黑樹的程式:
這裡首先解決一個最棘手的問題,如何判斷一個路徑上面的黑節點相同? 我們很容易想到,可以先遍歷一條路徑找到一個基準值,然
後和其他路徑做比較,程式碼實現就是每次走到葉子結點的時候,比較該條路徑的黑色節點節點個數是否和基準值相等? 如果不相等
那麼返回false. 所以我們引數裡面需要一個m(傳遞黑色節點基準值)和n(記錄該條路徑上面的黑色節點個數).
那麼我們來瞧瞧程式碼實現:
bool ISRBtree()
{
if (_root->_col == RED)
{
return false;
}
size_t n = 0;
size_t m = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
n++;
}
cur = cur->_left;
}
return _ISRBtree(_root, m, n);
}
bool _ISRBtree(Node* root, size_t m, size_t n)
{
if (root == NULL)
{
if (m == n)
return true;
else
return false;
}
if (root->_col == BLACK)
{
m++;
}
if (root->_col == RED && root->_parent && root->_parent->_col == RED)
{
return false;
}
return _ISRBtree(root->_left, m, n) && _ISRBtree(root->_right, m, n);
}
演示結果:
總結
紅黑樹是一個非常重要的資料結構,現在生活當中的使用我們就能看的出來,可能我們並不需要它的底層構造但是我們要學習一個語
言. 最快的途徑就是大師寫出來的程式碼,手頭都是資料我們要學習別人的巧妙的地方. 多看原始碼多多構造經典的演算法,或者容器.
嘗試自己編寫程式碼,理解那個框架, 這樣對我們的進步都是非常有用的.紅黑樹目前我就總結這麼多,哪裡有不足歡迎大家來指出來
,我會虛心改正.
所有程式碼實現:
#include<iostream>
#include<Windows.h>
#include<assert.h>
using namespace std;
enum colour
{
RED,
BLACK
};
template<class K, class V>
struct RBTreeNode
{
K _key;
K _val;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
colour _col;
RBTreeNode(const K& key, const V& val)
:_key(key)
, _val(val)
, _left(NULL)
, _right(NULL)
, _parent(NULL)
, _col(RED)
{}
};
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
RBTree()
:_root(NULL)
{}
bool Insert(const K& key, const V& val)
{
//_Insert(_root, x, y);
if (_root == NULL)
{
_root = new Node(key, val);
_root->_col = BLACK;
}
Node* cur = _root;
Node* parent = cur;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key == key)
{
return true;
}
}
if (parent->_key > key)
{
parent->_left = new Node(key, val);
parent->_left->_parent = parent;
cur = parent->_left;
}
else
{
parent->_right = new Node(key, val);
parent->_right->_parent = parent;
cur = parent->_right;
}
//目前父親節點,插入節點,叔叔節點已經就緒.
while(parent && parent->_col == RED)
{
Node* parentparent = parent->_parent;
Node* uncle = NULL;
if (parentparent->_left == parent)
uncle = parentparent->_right;
else
uncle = parentparent->_left;
if(uncle && uncle->_col == RED)
{
parentparent->_col = RED;
parent->_col = BLACK;
uncle->_col = BLACK;
cur = parentparent;
parent = cur->_parent;
}
else if (uncle == NULL || uncle->_col == BLACK)
{
if (parentparent->_left == parent)
{
if (parent->_left == cur)
{
RotateR(parentparent);
parent->_col = BLACK;
}
else
{
RotateLR(parentparent);
cur->_col = BLACK;
}
}
else
{
if (parent->_right == cur)
{
RotateL(parentparent);
parent->_col = BLACK;
}
else
{
RotateRL(parentparent);
cur->_col = BLACK;
}
}
parentparent->_col = RED;
if (parentparent == _root)
{
_root = parent;
}
}
else
{
assert(false);
}
if (_root->_col == RED)
{
_root->_col = BLACK;
}
}
return false;
}
bool ISRBtree()
{
if (_root->_col == RED)
{
return false;
}
size_t n = 0;
size_t m = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
n++;
}
cur = cur->_left;
}
return _ISRBtree(_root, m, n);
}
void print()
{
_print(_root);
}
protected:
void _print(Node* root)
{
if (root == NULL)
return;
_print(root->_left);
cout << root->_val << " ";
_print(root->_right);
}
bool _ISRBtree(Node* root, size_t m, size_t n)
{
if (root == NULL)
{
if (m == n)
return true;
else
return false;
}
if (root->_col == BLACK)
{
m++;
}
if (root->_col == RED && root->_parent && root->_parent->_col == RED)
{
return false;
}
return _ISRBtree(root->_left, m, n) && _ISRBtree(root->_right, m, n);
}
void RotateLR(Node*& parent)
{
RotateL(parent->_left);
RotateR(parent);
}
void RotateRL(Node*& parent)
{
RotateR(parent->_right);
RotateL(parent);
}
void RotateR(Node*& parent)
{
Node* subL = parent->_left;
Node* subLR = NULL;
if (subL)
subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (ppNode == NULL)
{
_root = subL;
_root->_parent = NULL;
}
else
{
if (ppNode->_left == parent)
ppNode->_left = subL;
else
ppNode->_right = subL;
subL->_parent = ppNode;
}
}
void RotateL(Node*& parent)
{
Node* subR = parent->_right;
Node* subRL = NULL;
if (subR)
subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (ppNode == NULL)
{
_root = subR;
_root->_parent = NULL;
}
else
{
if (ppNode->_left == parent)
ppNode->_left = subR;
else
ppNode->_right = subR;
subR->_parent = ppNode;
}
}
private:
Node* _root;
};
void Test()
{
RBTree<int, int> T;
//8 9 2 4 1 6 3 5
T.Insert(8, 8);
T.Insert(9, 9);
T.Insert(2, 2);
T.Insert(4, 4);
T.Insert(1, 1);
T.Insert(6, 6);
T.Insert(3, 3);
T.Insert(5, 5);
cout << "該二叉樹是否為紅黑樹??" << T.ISRBtree() << endl;
T.print();
system("pause");
}
相關推薦
資料結構 — 淺析紅黑樹原理以及實現
淺析紅黑樹原理以及實現我們在上一篇部落格認識到了平衡二叉樹(AVLTree),瞭解到平衡二叉樹的性質,其實平衡二叉樹最大的作用就是查詢,AVL樹的查詢、插入和刪除在平均和最壞情況下都是O(logn)。AVL樹的效率就是高在這個地方。如果在AVL樹中插入或刪除節點後,使得高度之
【資料結構】紅黑樹(如何實現及怎樣判斷)
紅黑樹是一顆二叉搜尋樹,它在每個節點上增加了一個儲存位來表示節點的顏色,可以是red或black。通過對任何一條從根節點到葉子節點的簡單路徑上的顏色來約束,紅黑樹保證了最長路徑不超過最短路經的兩倍,因此近似於平衡。 紅黑樹的規則: 1、每個節點不是紅色就是
資料結構之紅黑樹個人筆記
作者:Sky Wang 於 2013-08-08  
【資料結構】 紅黑樹
一、概念 Red-Black Tree 簡稱 R-B Tree,是一種自平衡二叉查詢樹,是在電腦科學中用到的一種資料結構,典型的用途是實現關聯陣列。 二、特性 (1)每個節點或者是黑色,或者是紅色。 (2)根節點是黑色。 (3)每個葉子節點(NIL)是黑
【資料結構】紅黑樹的插入(Insert)
前言: 紅黑樹是一棵二叉搜尋樹,它在每個節點上增加了一個儲存位來表示節點的顏色,可以是Red或Black。通過對任何一條從根到葉子簡單路徑上的顏色來約束,紅黑樹保證最長路徑不超過最短路徑的兩倍,因而近似於平衡。 紅黑樹的基本概念: 紅黑樹是滿足下面紅黑性質的二叉
linux核心分析--核心中的資料結構之紅黑樹(續)
#include<linux/rbtree.h> #include <linux/string.h> #include "kn_common.h" MODULE_LICENSE("Dual BSD/GPL"); struct student { int id;
linux核心分析--核心中的資料結構之紅黑樹(四)
紅黑樹由於節點顏色的特性,保證其是一種自平衡的二叉搜尋樹。 紅黑樹的一系列規則雖然實現起來比較複雜,但是遵循起來卻比較簡單,而且紅黑樹的插入,刪除效能也還不錯。 所以紅黑樹在核心中的應用非常廣泛,掌握好紅黑樹,即有利於閱讀核心原始碼,也可以在自己的程式碼中借鑑這種資料結構。 紅黑樹必
【資料結構】紅-黑樹
1.紅-黑樹的特徵 它主要有兩個特徵:1.節點都有顏色;2.在插入和刪除的過程中,要遵循保持這些顏色的不同排列的規則。首先第一個特徵很好解決,在節點類中店家一個數據欄位,例如boolean型變數,以此來表示節點的顏色資訊。第二個特徵比較複雜,紅-黑樹有它
資料結構之紅黑樹(二)——插入操作
插入或刪除操作,都有可能改變紅黑樹的平衡性,利用顏色變化與旋轉這兩大法寶就可應對所有情況,將不平衡的紅黑樹變為平衡的紅黑樹。 在進行顏色變化或旋轉的時候,往往要涉及祖孫三代節點:X表示操作的基準節點,P代表X的父節點,G代表X的父節點的父節點。 我們先來大體預覽一下插入的
使用核心資料結構:紅黑樹 rbtree
一、使用核心紅黑樹檔案rbtree 1.1 核心紅黑樹檔案 rbtree.h:/usr/src/kernels/2.6.32-279.el6.x86_64/include/linux/rbtree.h rbtree.c:/usr/src/ke
[從頭學數學] 第261節 Python實現資料結構:紅黑樹(RB Tree)
劇情提要:阿偉看到了一本比較有趣的書,是關於《計算幾何》的,2008年由北清派出版。很好奇它裡面講了些什麼,就來看看啦。 正劇開始: 星曆2016年09月09日 11:26:00, 銀河系厄爾斯星球中華帝國江南行省。 [工程師阿偉]正在和[機器小偉]一起研究[計算幾何]]。
資料結構之紅黑樹-動圖演示(上)
紅黑樹是比較常見的資料結構之一,在Linux核心中的完全公平排程器、高精度計時器、多種語言的函式庫(如,Java的TreeMap)等都有使用。 在學習紅黑樹之前,先來熟悉一下二叉查詢樹。 二叉查詢樹(Binary Search Tree) 二叉查詢樹,它有一個根節點,且每個節點下最多有隻能有兩個子節點,左子節
資料結構之紅黑樹-動圖演示(下)
節點的插入和刪除 我們知道變色和旋轉是為了修正被破壞的紅黑樹,使其符合紅黑樹的規則,從新達到平衡狀態。那麼增加或刪除節點在具體情況下該如何操作呢? 插入節點 紅黑樹的節點插入與二叉查詢樹的插入的過程是一樣的,只是最後多了一步平衡調整操作。 新插入的節點預設為紅色節點,所以新節點插入到黑色節點下時不需要對平衡調
紅黑樹原理解析以及Java實現
紅黑樹 本文的主要內容: 1、紅黑樹的基本概念以及最重要的5點規則。 2、紅黑樹的左旋轉、右旋轉、重新著色的原理與Java實現; 3、紅黑樹的增加結點、刪除結點過程解析; 1.紅黑樹的基本概念與資料結構表示 首先紅黑樹來個定義: 紅黑樹定
數據結構之紅黑樹
紅黑樹;Java數據結構之紅黑樹 紅黑樹介紹:紅黑樹是一個平衡的二叉樹,但不是一個完美的平衡二叉樹。雖然我們希望一個所有查找都能在~lgN次比較內結束,但是這樣在動態插入中保持樹的完美平衡代價太高,所以,我們稍微放松逛一下限制,希望找到一個能在對數時間內完成查找的數據結構。這個時候,紅黑樹站了出來。 1:
紅黑樹原理淺談(附Linux核心原始碼註釋)
引言:紅黑樹(英語:Red–black tree)是一種自平衡二叉查詢樹,是在電腦科學中用到的一種資料結構,典型的用途是實現關聯陣列。它是在1972年由魯道夫·貝爾發明的,他稱之為"對稱二叉B樹",它現代的名字是在Leo J. Guibas和Robert Sedgewick於19
STL原始碼剖析---紅黑樹原理詳解下
轉載請標明出處,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7760584 演算法導論書上給出的紅黑樹的性質如下,跟STL原始碼剖析書上面的4條性質大同小異。 1、每個結點或是紅色的,或是黑色的
STL原始碼剖析---紅黑樹原理詳解上
紅黑樹和我們以前學過的AVL樹類似,都是在進行插入和刪除操作時通過特定操作保持二叉查詢樹的平衡,從而獲得較高的查詢效能。不過自從紅黑樹出來後,AVL樹就被放到了博物館裡,據說是紅黑樹有更好的效率,更高的統計效能。這一點在我們瞭解了紅黑樹的實現原理後,就會有更加深切的體
AVL平衡二叉樹,紅黑樹原理。
二叉搜尋樹 插入和刪除操作必須先查詢,查詢效率代表了二叉搜尋樹中各個操作的效能 最優情況:二叉搜尋樹為完全二叉樹,比較次數Log2^N 最壞情況:二叉搜尋樹為單支樹,平均比較次數N/2 平衡二叉樹 平衡樹: AVL樹,紅黑樹 AVL樹:(二叉搜尋樹改良版)
二叉樹之一BST樹,AVL樹詳解及B樹和紅黑樹原理分析
BST樹,AVL樹詳解及B樹和紅黑樹原理分析 網際網路面試中樹尤其是BST,AVL是提問的重點也是難點,甚至B樹乃至高階資料結構紅黑樹都是提問的重點,像阿里雲面試就曾經問過map實現機制(紅黑樹)及其原理,這裡我們要做到對BST/AVL完全熟悉能給出全部程式碼實現,紅黑樹、