紅黑樹

紅黑樹是內核裏面最經常使用的數據結構,紅黑樹自己也是一個很是複雜的數據結構。我本身爲了詳細的理解紅黑樹的流程和原理弄了好幾天,果真已經老了,再加上智商有限,真是。。無論怎樣,不管是看wikipedia仍是看其餘人寫的教程,我以爲都不是很好懂。固然,紅黑樹自己就不是太好懂,本身不畫是很難理解的。node

可是在網上不太好找紅黑樹的代碼,即使紅黑樹的原理講的再清楚,也只是『僞裝』本身理解了。怎麼說,就是不太理解吧,由於寫起來也很差寫,一會兒內存越界了什麼的狀況不少。若是對紅黑樹不感興趣,只須要直到這是一個可以自平衡的樹,概念依舊不想了解的話,只要知道紅黑樹的插入、刪除和搜索的算法複雜度都是O(log n),是至關快的。若是你有需求是大量的插入刪除還要保證有序的話,那可使用紅黑樹。git

代碼能夠直接看這裏GISTgithub

若是你有點興趣,就往下看,咱們這裏簡單規定:算法

  1. n是新結點。數據結構

  2. r是根結點。函數

  3. x某個結點。優化

  4. p是某個結點的父結點(x->parent)。spa

  5. g是某個結點的祖父結點(x->parent->parent)。指針

  6. s是某個結點的兄弟結點(x->parent->left或right)。code

  7. u是某個節點的叔叔節點(g->left或right)。

其中s和u兩個節點具體是left或者right能夠根據實際狀況來判斷。好比當前節點爲右孩子,那麼兄弟節點確定就是父節點的左孩子。那麼叔叔節點也同理了。

二叉查找樹

紅黑樹繼承二叉查找樹的性質,這裏簡單的說一下二叉查找樹的性質,具體解釋看Wikipedia,須要詳細的理解二叉查找樹。

這裏就很是很是簡單的講解一下二叉查找樹的一點點簡單的概念,不涉及深度優化什麼的。

插入

簡單的來講用圖示來解釋,插入過程以下:

  1. 若是是空樹,則直接做爲根(root)結點。

  2. 若是不是空樹,則從根搜尋,若是值小於當前結點(x),可是當前結點有左孩子,則繼續找當前結點的左孩子(x=x->left)。直到當前結點沒有左孩子(x->left==NULL),新結點(r)做爲當前結點的左孩子。

  3. 若是不是空樹,則從根搜尋,若是值大於當前結點(x),可是當前結點有右孩子,則繼續找當前結點的右孩子(x=x->left)。直到當前結點沒有右孩子(x->left==NULL),新結點(r)做爲當前結點的右孩子。

具體步驟以下所示:

system

分解成下面步驟:

  1. 根節點爲空,插入10。

  2. 插入9,比10小,而且10沒有左孩子,則9爲10的左孩子。

  3. 插入20,比10大,而且10沒有右孩子,則20爲10的右孩子。

  4. 插入8,比10小,10右左孩子,繼續查找10的左孩子9,9沒有左孩子,則8做爲9的左孩子。

  5. 插入13,比10大,10有右孩子,找到20,比20小,而且20沒有左孩子,則13成爲20的左孩子。

代碼以下:

int put(struct node *root, const int value){     struct node *p = root;     struct node *c = (node*)malloc(sizeof(node));     c->left = NULL;     c->right = NULL;     c->parent = NULL;     c->value = value;       while(p){         if(value==p->value) break;         if(value<p->value){             if(!p->left){                 p->left = c;                 c->parent = p;                 break;             }             p = p->left;         } else if(value>p->value){             if(!p->right){                 p->right = c;                 c->parent = p;                 break;             }             p = p->right;         }     }       return 1; }

刪除

刪除二叉查找樹的一個結點方法以下:

  1. 若是刪除的節點沒有左右孩子,則直接刪除。

  2. 若是要刪除一個節點,則查找右子樹中的最小值或者左子樹中的最大值,與要刪除的節點交換。

system

刪除一個根節點24或者根節點8


system

刪除一個有子樹的節點20,交換後依舊能保持搜索樹的性質

上面的圖中,不管是右子樹的最小值仍是左子樹的最大值均可以。若是沒有右子樹或者左子樹,則用右子樹和左子樹的根替換便可。圖中刪除節點20的右子樹爲以30爲根節點的子樹。左子樹爲以13爲根節點的子樹。因此右子樹的最小值爲24,替換20,再刪除20。左子樹的最大值爲13,則替換20。

任意一種方法均可以。

代碼以下:

struct node *find_trans(struct node *root){     /*     若是要刪除一個結點,則查找     右子樹中的最小值     或者     左子樹中的最大值     與要刪除的結點交換      */     struct node *p = root;     if(p->left){         p = p->left;         while(p->right){             p = p->right;         }     } else {         p = p->right;         while(p->left){             p = p->left;         }     }     return p; }

刪除代碼以下:

int del(struct node *root, const int value){     struct node *p = root;     while(p){         if(value==p->value){             printf("hit and removed %d\n", p->value);             /* remove */             if(!p->left&&!p->right){                 /* nil child */                 if(p->value>p->parent->value){                     /*at right*/                     p->parent->right = NULL;                 } else {                     p->parent->left = NULL;                 }             } else {                 node *tmp = find_trans(p);                 if(p->value>p->parent->value){                     /*at right*/                     p->parent->right = tmp;                 } else {                     p->parent->left = tmp;                 }                 tmp->parent = p->parent;             }             p->value = NULL;             p->left=NULL;             p->right=NULL;             p->parent=NULL;             free(p);             return 1;         }         if(value<p->value){             p = p->left;         } else if(value>p->value){             p = p->right;         }     }     return 0; }

具體的代碼在這個GIST中。這是一個很是很是簡單的搜索樹,並無作什麼優化,也有可能有的地方有bug,但這裏只是爲了方便理解。

紅黑樹

紅黑樹是創建在二叉搜索樹之上的一種樹,這個樹必須依賴一下幾種性質,一旦任意一個性質被破壞了,就不是紅黑樹了。Wikipedia頁面在此

  1. 節點是紅色或黑色。

  2. 根是黑色。

  3. 全部nil節點都是黑色,也能夠說nil節點都爲黑色的NULL節點。

  4. 每一個紅色節點的兩個子節點都是黑色。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點)

  5. 從任一節點到其每一個葉子的全部簡單路徑都包含相同數目的黑色節點。

這裏主要說一下這其中的性質裏容易混淆的。

其中性質3能夠這麼看,葉子節點就是二叉樹裏最下面的節點的一個空節點,能夠被稱爲NIL節點。以下圖。

system

這裏的-1這個節點的兩個nil節點都是黑色,也就是說倒數一層的節點是紅色的。但其實沒有關係,有沒有這個nil節點都沒有關係,爲了簡單,後面的圖我就不畫這些nil節點了。能夠默認認爲,每一個新的節點都有兩個nil節點,這兩個nil節點其實都是NULL節點。這裏必須理解。

再強調一下,若是認爲最後一層節點沒有nil節點,而左右都是NULL的話,那麼最後一層必須是紅色。若是認爲最後一層節點都有默認的nil節點,即左子樹和右子樹都有一個nil節點,值可能也是NULL,那麼nil節點必須是黑色的,『最後一層』節點爲紅色。

其中性質5也要提一下,從上圖來看,從根節點到最下層的節點的任意路徑的黑色節點數都相同。例如上圖中的,4->2->0->-1就有兩個黑節點,同理4->2->三、4->10->5和4->10->11->12。

咱們定義節點以下:

int BLACK = 0; int RED = 1; struct node {     struct node *left;     struct node *right;     struct node *parent;     int value;     int color; //0 is black, 1 is red }; struct rbtree {     struct node *root; };

旋轉節點

在紅黑樹中另外一個很是重要的概念就是節點的旋轉,有左旋和右旋。每一次左旋和右旋以後都會生成新的子樹,圖以下。

system

須要注意一點的是,上面示意圖裏的旋轉節點裏面的X和Y都有子節點,但若是X,Y沒有子節點的話,能夠想象成nil,也就是NULL。雖然a、b、c均可能爲NULL,但不表明只是交換了X和Y的值。必須是旋轉了。只是Y的左孩子NULL變成了X的右孩子NULL。

其中代碼以下:

void replace_node(struct rbtree *t, struct node *o,     struct node *n) {     if (o->parent == NULL) {         t->root = n;     } else {         if (o == o->parent->left)             o->parent->left = n;         else             o->parent->right = n;     }     if (n != NULL) {         n->parent = o->parent;     } } void rotate_left(struct rbtree *t, struct node *n) {     // 左旋     struct node *r = n->right;     replace_node(t, n, r);     n->right = r->left;     if (r->left != NULL) {         r->left->parent = n;     }     r->left = n;     n->parent = r; } void rotate_right(struct rbtree *t, struct node *n) {     // 右旋     struct node *l = n->left;     replace_node(t, n, l);     n->left = l->right;     if (l->right != NULL) {         l->right->parent = n;     }     l->right = n;     n->parent = l; }

插入

紅黑樹的插入和刪除都比較複雜,但插入相比刪除來講已經算是簡單的了。全部插入的新節點的顏色都爲黑色插入能夠包含5個情形,咱們能夠簡單的使用insert_case_n。這幾個case的定義以下。

若是在平衡過程當中根節點變爲紅色,則能夠直接把根節點變爲黑色。

static void insert_case1(struct rbtree *t, struct node *n); static void insert_case2(struct rbtree *t, struct node *n); static void insert_case3(struct rbtree *t, struct node *n); static void insert_case4(struct rbtree *t, struct node *n); static void insert_case5(struct rbtree *t, struct node *n);

狀況1:

新節點位於樹的根上,這種狀況下,比較簡單,就一個節點,直接變爲黑色便可。以下圖。

system

1節點做爲根節點則直接變爲黑色

代碼以下:

void insert_case1(struct rbtree *t, struct node *n) {     if (n->parent == NULL)         n->color = BLACK;     else         insert_case2(t, n); }

狀況2:

若是新節點的根節點顏色是黑色,則就沒有違反任何性質,由於新節點是紅色的,在任何一個簡單路徑都沒有增長黑色節點的個數。以下圖。

system

不管是增長一個2節點仍是0節點,都沒有違反任何性質

上圖的0節點只是舉例,咱們仍是按照插入2節點來講。

代碼以下:

void insert_case2(struct rbtree *t, struct node *n) {     if (node_color(n->parent) == BLACK)         return; /* Tree is still valid */     else         insert_case3(t, n); }

狀況3:

若是新節點的父節點是紅色,如上圖中若是要插入一個3,那麼3的父節點2節點也是紅色,就違反了紅黑樹的性質,必須從新繪製。那麼分這幾種狀況。

若是父節點P和叔叔節點U都是紅色

若是父節點P和叔叔節點U都是紅色節點,那麼就:

  1. 祖父節點變爲紅色。

  2. 把父節點變爲黑色。

  3. 把叔叔節點變爲黑色。

以下圖:

system

改變節點顏色

這個時候祖父節點G就是紅色了。通常來講,祖父節G點是黑色,那麼祖父節點G的父節點G->parent必須是紅色節點,就又遇到這種狀況。這個時候把指針指向祖父節點,而後再執行狀況3。若是不知足這個條件了,就執行狀況4。

若是按照上圖的狀況,1節點是根節點,則直接將1節點塗爲黑色便可。

代碼以下:

void insert_case3(struct rbtree *t, struct node *n) {     if (node_color(uncle(n)) == RED) {         n->parent->color = BLACK;         uncle(n)->color = BLACK;         grandparent(n)->color = RED;         insert_case1(t, grandparent(n));     } else {         insert_case4(t, n);     } }

狀況4:

父節點P是紅色但叔叔節點U是黑色或者沒有

若是父節點P是紅色可是叔叔節點U是黑色或者沒有,新節點是父節點的一種不一樣順序的節點。好比父節點P是右孩子,但新節點N是左孩子;好比父節點是左孩子,但新節點是右孩子。那麼:

(對,我知道很煩,但確實很麻煩)

  1. 新節點N是父節點P的右孩子,而P是其父節點的左孩子,那麼左旋P。

  2. 新結點N是父節點P的左孩子,而P是其父節點的右孩子,那麼右旋P。

以下圖:

system

注意裏面不僅是把節點的值和顏色改了,是左旋和右旋

其中上面一部分是新結點N是父節點P的左孩子,而P是其父節點的右孩子的狀況,下面一部分是新節點N是父節點P的右孩子,而P是其父節點的左孩子的狀況。

代碼以下:

void insert_case4(struct rbtree *t, struct node *n) {     if (n == n->parent->right && n->parent == grandparent(n)->left) {         rotate_left(t, n->parent);         n = n->left;     } else if (n == n->parent->left && n->parent == grandparent(n)->right) {         rotate_right(t, n->parent);         n = n->right;     }     insert_case5(t, n); }

狀況5:

父節點P是紅色但叔叔節點U是黑色或者沒有

若是不是上面的不一樣順序節點,而是相同順序節點的話,就是狀況5。也就是說,父節點P是右孩子,新節點N也是父節點的右孩子;父節點P是左孩子,新節點P也是父節點的左孩子,那就符合這種狀況。那麼:

  1. 新節點N和父節點P都是左子節點,那麼右旋祖父節點。

  2. 新節點N和父節點P都是右子節點,那麼左旋祖父節點。

在旋轉完畢後,切換父節點和祖父節點的顏色。

以下圖:

system

注意裏面不僅是把節點的值和顏色改了,是左旋和右旋

代碼以下:

void insert_case5(struct rbtree *t, struct node *n) {     n->parent->color = BLACK;     grandparent(n)->color = RED;     if (n == n->parent->left && n->parent == grandparent(n)->left) {         rotate_right(t, grandparent(n));     } else {         rotate_left(t, grandparent(n));     } }

注意裏面不僅是把節點的值和顏色改了,是左旋和右旋,這個必須強調,若是不理解能夠去Wikipedia看帶nil節點的圖。

在處理完整個狀況以後,咱們的插入節點的代碼以下:

void insert_node(struct rbtree *t, int value){     struct node *n =new_node(value, RED, NULL, NULL);     if (t->root == NULL) {         t->root = n;     } else {         struct node *r = t->root;         while (1) {             if (value == r->value) {                 //value is root value                 return;             } else if(value > r->value){                 if (r->right == NULL) {                     r->right = n;                     break;                 } else {                     r = r->right;                 }             } else if(value < r->value){                 if (n->left == NULL) {                     if (r->left == NULL) {                         r->left = n;                         break;                     } else {                         r = r->left;                     }                 }             }         }         n->parent = r;     }     insert_case1(t, n); }

插入步驟

如今咱們能夠模擬插入步驟,一步一步的看是如何插入的:

int main(){     struct rbtree *t = create();     insert_node(t, 1);     insert_node(t, 2);     insert_node(t, 3);     insert_node(t, 4);     insert_node(t, 5);     print(t);     return 0; }

下圖描繪了插入一、二、三、四、5整個紅黑樹的過程。

system

來加上插入十二、11的過程。

system

基本上整個插入過程就是這樣的了,相比刪除仍是比較簡單的。

刪除

紅黑樹的刪除是最爲複雜的,但不管怎樣咱們已經看過了插入,插入雖然已經很困難,但按照插入的邏輯來走,應該是能夠理解的。刪除也有好幾個情形,比插入多一個,一共6個情形。其實插入和刪除本質上來講沒有那麼多狀況,只是分的比較細而已,就拿插入來講,能夠說總共就三種,只是最後的一種還衍生出多的兩種。

刪除定義以下:

static void delete_case1(struct rbtree *t, struct node *n); static void delete_case2(struct rbtree *t, struct node *n); static void delete_case3(struct rbtree *t, struct node *n); static void delete_case4(struct rbtree *t, struct node *n); static void delete_case5(struct rbtree *t, struct node *n); static void delete_case6(struct rbtree *t, struct node *n);

上面的函數咱們會一個一個的講解,除此以外還有一個很是重要的函數replace_node,用來替換節點,代碼以下。

void replace_node(struct rbtree *t, struct node *o, struct node *n) {     if (o->parent == NULL) {         t->root = n;     } else {         if (o == o->parent->left)             o->parent->left = n;         else             o->parent->right = n;     }     if (n != NULL) {         n->parent = o->parent;     } }

狀況1:

若是要刪除的節點是一個根節點,什麼也不作的返回,不然執行刪除的狀況2。

void delete_case1(struct rbtree *t, struct node *n) {     if (n->parent == NULL)         return;     else         delete_case2(t, n); }

狀況2:

兄弟節點S的顏色爲紅色

若是兄弟節點S的顏色爲紅色,那麼把父節點P的顏色變爲紅色,把兄弟節點S的顏色變爲黑色。若是要刪除的節點是左孩子,那麼左旋要刪除節點的父節點P。若是要刪除的節點是右孩子,那麼右旋要刪除節點的父節點P。

system

如圖所示,但執行了這一步以後尚未完,和插入不同,刪除還必須進入後續狀況處理。

代碼以下:

void delete_case2(struct rbtree *t, struct node *n) {     if (node_color(sibling(n)) == RED) {         n->parent->color = RED;         sibling(n)->color = BLACK;         if (n == n->parent->left)             rotate_left(t, n->parent);         else             rotate_right(t, n->parent);     }     delete_case3(t, n); }

狀況3:

N的父節點P、叔叔節點S和S的兒子節點都是黑色

這種狀況下,只須要把叔叔節點的顏色變爲紅色,而後將指針指向父節點P,並進入第一種刪除狀況。由於叔叔節點變爲紅色以後,經過他的路徑都少了一個黑色節點。

system

代碼以下:

void delete_case3(struct rbtree *t, struct node *n) {     if (node_color(n->parent) == BLACK &&         node_color(sibling(n)) == BLACK &&         node_color(sibling(n)->left) == BLACK &&         node_color(sibling(n)->right) == BLACK)     {         sibling(n)->color = RED;         delete_case1(t, n->parent);     }     else         delete_case4(t, n); }

若是不知足上面的狀況,則進入狀況4(對,其實我寫的也很累了)。

狀況4:

叔叔節點S和S的兒子都是黑色,可是父節點P是紅色

這種狀況下,咱們簡單的交換刪除節點的父節點P和兄弟節點S的顏色。

system

代碼以下:

void delete_case4(struct rbtree *t, struct node *n) {     if (node_color(n->parent) == RED &&         node_color(sibling(n)) == BLACK &&         node_color(sibling(n)->left) == BLACK &&         node_color(sibling(n)->right) == BLACK)     {         sibling(n)->color = RED;         n->parent->color = BLACK;     }     else         delete_case5(t, n); }

若是不符合上面狀況,則進入狀況5。

狀況5:

若是叔叔節點S是黑色,叔叔節點S的右兒子是黑色,而刪除節點N是它父親節點P的左孩子,則在叔叔節點S上作右旋。反之若是叔叔節點S是黑色,叔叔節點S的左兒子是黑色,而刪除節點N是它父親節點P的右孩子,則在叔叔節點S上作左旋。

若是刪除的節點N是左孩子,把叔叔節點的顏色變爲紅色,叔叔節點的左孩子顏色變爲黑色。反之亦然。

system

代碼以下:

void delete_case5(struct rbtree *t, struct node *n) {     if (n == n->parent->left &&         node_color(sibling(n)) == BLACK &&         node_color(sibling(n)->left) == RED &&         node_color(sibling(n)->right) == BLACK)     {         sibling(n)->color = RED;         sibling(n)->left->color = BLACK;         rotate_right(t, sibling(n));     }     else if (n == n->parent->right &&              node_color(sibling(n)) == BLACK &&              node_color(sibling(n)->right) == RED &&              node_color(sibling(n)->left) == BLACK)     {         sibling(n)->color = RED;         sibling(n)->right->color = BLACK;         rotate_left(t, sibling(n));     }     delete_case6(t, n); }

若是不符合上面的狀況則直接進入狀況6,最後的一種狀況。

狀況6:

若是叔叔節點S是黑色,叔叔節點S的右兒子是紅色,而刪除節點N是父節點P的左孩子,則在父節點P作左旋。反之若是叔叔節點S是黑色,叔叔節點S的左兒子是紅色,而刪除節點N是父節點P的右孩子,則在父節點P作右旋。

若是刪除的節點N是左孩子,則把叔叔節點的右孩子變爲黑色再旋轉父節點,反之亦然。

system

代碼以下:

void delete_case6(struct rbtree *t, struct node *n) {     sibling(n)->color = node_color(n->parent);     n->parent->color = BLACK;     if (n == n->parent->left) {         sibling(n)->right->color = BLACK;         rotate_left(t, n->parent);     }     else     {         sibling(n)->left->color = BLACK;         rotate_right(t, n->parent);     } }

這樣咱們幾種刪除的狀況就完成了。完成後刪除代碼以下:

void delete_node(struct rbtree *t, int value){     struct node *child;     struct node *n = search(t, value);     if (n == NULL) {         return;     }     if (n->left != NULL && n->right !=NULL ){         struct node *pred = maximum_node(n->left);         n->value = pred->value;         n = pred;     }          child = n->right == NULL ? n->left : n->right;     if (node_color(n) == BLACK) {         n->color = node_color(child);         delete_case1(t, n);     }     replace_node(t, n, child);     if (n->parent == NULL && child != NULL) {         child->color = BLACK;     }     free(n); }

刪除過程以下:

system

基本上咱們紅黑樹就講解完了,若是有任何不理解的地方,請給我發郵件,也歡迎提PR修正裏面的錯誤內容。

相關文章
相關標籤/搜索