1. 程式人生 > >linux核心分析--核心中的資料結構之紅黑樹(四)

linux核心分析--核心中的資料結構之紅黑樹(四)

紅黑樹由於節點顏色的特性,保證其是一種自平衡的二叉搜尋樹。

紅黑樹的一系列規則雖然實現起來比較複雜,但是遵循起來卻比較簡單,而且紅黑樹的插入,刪除效能也還不錯。

所以紅黑樹在核心中的應用非常廣泛,掌握好紅黑樹,即有利於閱讀核心原始碼,也可以在自己的程式碼中借鑑這種資料結構。

紅黑樹必須滿足的規則:

所有節點都有顏色,要麼紅色,要麼黑色
根節點是黑色,所有葉子節點也是黑色
葉子節點中不包含資料
非葉子節點都有2個子節點
如果一個節點是紅色,那麼它的父節點和子節點都是黑色的
從任何一個節點開始,到其下葉子節點的路徑中都包含相同樹木的黑節點
紅黑樹中最長的路徑就是紅黑交替的路徑,最短的路徑是全黑節點的路徑,再加上根節點和葉子節點都是黑色,

從而可以保證紅黑樹中最長路徑的長度不會超過最短路徑的2倍。

一、相關檔案在核心中的位置

核心中關於紅黑樹定義的標頭檔案位於:<linux/rbtree.h> include/linux/rbtree.h

標頭檔案中定義的函式的實現位於:lib/rbtree.c

核心中紅黑樹的使用和連結串列(list)有些類似,是將紅黑樹的節點放入自定義的資料結構中來使用的。

首先需要注意的一點是紅黑樹節點的定義:

struct rb_node
{
    unsigned long  rb_parent_color;
#define    RB_RED        0
#define    RB_BLACK    1
    struct rb_node *rb_right;
    struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));

剛開始看到這個定義的時候,我覺得很奇怪,等到看懂了之後,才知道原來作者巧妙的利用記憶體對齊來將2個內容存入到一個欄位中(不服不行啊^_^!)。

欄位 rb_parent_color 中儲存了2個資訊:

父節點的地址
本節點的顏色
這2個資訊是如何存入一個欄位的呢?主要在於 __attribute__((aligned(sizeof(long))));

這行程式碼的意思就是 struct rb_node 在記憶體中的地址需要按照4 bytes或者8 bytes對齊。

注:sizeof(long) 在32bit系統中是4 bytes,在64bit系統中是8 bytes

 

struct rb_node的地址按4 bytes對齊,意味著分配的地址都是4的倍數。

4 的二進位制為 100 ,所以申請分配的 struct rb_node 的地址的最後2位始終是零,

struct rb_node 的欄位 rb_parent_color 就是利用最後一位來儲存節點的顏色資訊的。

(有時候不禁就想感慨一下,太巧妙了!!太精妙了!!)
 

/* rb_parent_color 儲存了父節點的地址和本節點的顏色 */
 
/* 將 rb_parent_color 的最後2位置成0,即將顏色資訊去掉,剩下的就是parent節點的地址 */
#define rb_parent(r)   ((struct rb_node *)((r)->rb_parent_color & ~3))
 
/* 取得 rb_parent_color 二進位制表示的最後一位,即用於儲存顏色資訊的那一位 */
#define rb_color(r)   ((r)->rb_parent_color & 1)
 
/* 將 rb_parent_color 二進位制表示的最後一位置為0,即置為紅色 */
#define rb_set_red(r)  do { (r)->rb_parent_color &= ~1; } while (0)
 
/* 將 rb_parent_color 二進位制表示的最後一位置為1,即置為黑色 */
#define rb_set_black(r)  do { (r)->rb_parent_color |= 1; } while (0)

在剛開始看的時候就沒怎麼想明白這個事,明白了4位元組對齊的問題再看上面的標頭檔案就比較輕鬆了!

還有需要重點看的就是rb_tree.c中的5個函式,下面對這5個函式進行一些註釋:

二、函式介紹

函式1:左旋操作,當右子樹的長度過大導致樹不平衡時,進行左旋操作:

/*
 *  左旋操作其實就3個動作:見圖left
 *  1. node的右子樹關聯到right的左子樹
 *  2. right的左子樹關聯到node
 *  3. right取代node的位置
 *  其他帶程式碼都是一些相應的parent指標的變化
 */
static void __rb_rotate_left(struct rb_node *node, struct rb_root *root)
{
    /* 初始化相對於node節點的父節點(圖中的P)和右節點(圖中的R) */
    struct rb_node *right = node->rb_right;
    struct rb_node *parent = rb_parent(node);
 
    /* 步驟1  */
    if ((node->rb_right = right->rb_left))
        rb_set_parent(right->rb_left, node);
 
    /* 步驟2 */
    right->rb_left = node;
    rb_set_parent(right, parent);
 
    /* node的parent NOT NULL 時,right取代原先的node的位置 */
    if (parent)
    {
        if (node == parent->rb_left)
            parent->rb_left = right;
        else
            parent->rb_right = right;
    }
 
    /* node的parent NULL 時,說明node原先時root節點,將新的root指向root即可 */
    else
        root->rb_node = right;
    rb_set_parent(node, right);
}

左旋操作圖解:

函式2:右旋操作,和左旋操作類似。

函式3:追加節點後,設定此節點的顏色。

/*
 *  本函式沒有插入節點的功能,只是在插入新節點後,設定新節點的顏色,從而保證紅黑樹的平衡性。
 *  新插入的節點預設都是紅色的。
 *  
 *  下面的程式碼看著複雜,其實只要時時記住紅黑樹的幾個重要特性,就會發現下面的都是在儘量保持住紅黑樹的這些特性。
 *  1. 無論從哪個節點開始,到其葉子節點的路徑中包含的黑色節點個數時一樣的
 *  2. 不能有連續的2個紅色節點,即父節點和子節點不能同時為紅色
 *  所以最簡單的情況就是:插入節點的父節點是黑色的。那麼插入一個紅節點後不會有任何影響。
 *  3. 左旋操作有減少右子樹高度的作用
 *  4. 同理,右旋操作有減少左子樹高度的作用
 */
void rb_insert_color(struct rb_node *node, struct rb_root *root)
{
    struct rb_node *parent, *gparent;
 
    while ((parent = rb_parent(node)) && rb_is_red(parent))
    {
        gparent = rb_parent(parent);
 
        /* parent 是 gparent的左子樹時 */
        if (parent == gparent->rb_left)
        {
            {
                /* gparent的左右子樹的黑色節點都增加一個,仍然平衡 */
                register struct rb_node *uncle = gparent->rb_right;
                if (uncle && rb_is_red(uncle))
                {
                    rb_set_black(uncle);
                    rb_set_black(parent);
                    rb_set_red(gparent);
                    node = gparent;
                    continue;
                }
            }
 
            /* node為parent右子樹時 */
            if (parent->rb_right == node)
            {
                register struct rb_node *tmp;
                /* 左旋後,parent的位置被node取代,然後再交換parent和node的位置,
                 * 相當於node是parent的左子樹
                 * 由於node和parent都是紅色(否則到不了這一步),parent左右子樹的黑色節點數仍然是相等的
                 */
                __rb_rotate_left(parent, root);
                tmp = parent;
                parent = node;
                node = tmp;
            }
 
            /* parent 紅->黑,gparent左子樹比右子樹多一個黑色節點
             * 右旋後,gparent左子樹高度減一,減少的節點即parent,減少了一個黑色節點,parent變為新的gparent。
             * 所以右旋後,新的gparent的左右子樹的黑色節點數再次平衡了
             */
            rb_set_black(parent);
            rb_set_red(gparent);
            __rb_rotate_right(gparent, root);
        /* parent 是 gparent的右子樹時,和上面的過程類似 */
        } else {
            {
                register struct rb_node *uncle = gparent->rb_left;
                if (uncle && rb_is_red(uncle))
                {
                    rb_set_black(uncle);
                    rb_set_black(parent);
                    rb_set_red(gparent);
                    node = gparent;
                    continue;
                }
            }
 
            if (parent->rb_left == node)
            {
                register struct rb_node *tmp;
                __rb_rotate_right(parent, root);
                tmp = parent;
                parent = node;
                node = tmp;
            }
 
            rb_set_black(parent);
            rb_set_red(gparent);
            __rb_rotate_left(gparent, root);
        }
    }
 
    rb_set_black(root->rb_node);
}

函式4:刪除一個節點,並且調整刪除後各節點的顏色。其中調整節點顏色其實是另一個單獨的函式。

/* 刪除節點時,如果被刪除的節點左子樹==NULL或右子樹==NULL或左右子樹都==NULL
 * 那麼只要把被刪除節點的左子樹或右子樹直接關聯到被刪節點的父節點上即可,剩下的就是調整各節點顏色。
 * 只有被刪節點是黑色才需要調整顏色,因為刪除紅色節點不影響紅黑樹的特性。
 *
 * 被刪節點左右子樹都存在的情況下,其實就是用中序遍歷中被刪節點的下一個節點來替代被刪節點。
 * 程式碼中的操作只是將各個指標指向新的位置而已。
 */
void rb_erase(struct rb_node *node, struct rb_root *root)
{
    struct rb_node *child, *parent;
    int color;
 
    if (!node->rb_left)
        child = node->rb_right;
    else if (!node->rb_right)
        child = node->rb_left;
    else
    {
        struct rb_node *old = node, *left;
 
        /* 尋找中序遍歷中被刪節點的下一個節點 */
        node = node->rb_right;
        while ((left = node->rb_left) != NULL)
            node = left;
 
        /* 替換要刪除的節點old */
        if (rb_parent(old)) {
            if (rb_parent(old)->rb_left == old)
                rb_parent(old)->rb_left = node;
            else
                rb_parent(old)->rb_right = node;
        } else
            root->rb_node = node;
 
        child = node->rb_right;
        parent = rb_parent(node);
        color = rb_color(node);
 
        if (parent == old) {
            parent = node;
        } else {
            if (child)
                rb_set_parent(child, parent);
            parent->rb_left = child;
 
            node->rb_right = old->rb_right;
            rb_set_parent(old->rb_right, node);
        }
 
        node->rb_parent_color = old->rb_parent_color;
        node->rb_left = old->rb_left;
        rb_set_parent(old->rb_left, node);
 
        goto color;
    }
 
    parent = rb_parent(node);
    color = rb_color(node);
 
    if (child)
        rb_set_parent(child, parent);
    if (parent)
    {
        if (parent->rb_left == node)
            parent->rb_left = child;
        else
            parent->rb_right = child;
    }
    else
        root->rb_node = child;
 
 color:
    if (color == RB_BLACK)
        __rb_erase_color(child, parent, root);
}

函式5:刪除一個黑色節點後,重新調整相關節點的顏色。

/* 這裡的node就是上面函式中的child,所有node節點的左右子樹肯定都是NULL
 * 不滿足紅黑樹規則的就是從parent節點開始的子樹,只要給從parent開始的子樹增加一個黑色節點就行
 * 如果從parent節點開始的節點全是黑色,node和parent都繼續向上移動
 */
static void __rb_erase_color(struct rb_node *node, struct rb_node *parent,
                 struct rb_root *root)
{
    struct rb_node *other;
 
    /* (node不為NULL 且 node是黑色的) 或者 node == NULL */
    while ((!node || rb_is_black(node)) && node != root->rb_node)
    {
        if (parent->rb_left == node)
        {
            other = parent->rb_right;
            if (rb_is_red(other))
            {
                rb_set_black(other);
                rb_set_red(parent);
                __rb_rotate_left(parent, root);
                other = parent->rb_right;
            }
            /* 如果從parent節點開始的節點全是黑色,node和parent都繼續向上移動 */
            if ((!other->rb_left || rb_is_black(other->rb_left)) &&
                (!other->rb_right || rb_is_black(other->rb_right)))
            {
                rb_set_red(other);
                node = parent;
                parent = rb_parent(node);
            }
            else
            {
                if (!other->rb_right || rb_is_black(other->rb_right))
                {
                    rb_set_black(other->rb_left);
                    rb_set_red(other);
                    __rb_rotate_right(other, root);
                    other = parent->rb_right;
                }
                rb_set_color(other, rb_color(parent));
                rb_set_black(parent);
                rb_set_black(other->rb_right);
                __rb_rotate_left(parent, root);
                node = root->rb_node;
                break;
            }
        }
        else
        {
            other = parent->rb_left;
            if (rb_is_red(other))
            {
                rb_set_black(other);
                rb_set_red(parent);
                __rb_rotate_right(parent, root);
                other = parent->rb_left;
            }
            if ((!other->rb_left || rb_is_black(other->rb_left)) &&
                (!other->rb_right || rb_is_black(other->rb_right)))
            {
                rb_set_red(other);
                node = parent;
                parent = rb_parent(node);
            }
            else
            {
                if (!other->rb_left || rb_is_black(other->rb_left))
                {
                    rb_set_black(other->rb_right);
                    rb_set_red(other);
                    __rb_rotate_left(other, root);
                    other = parent->rb_left;
                }
                rb_set_color(other, rb_color(parent));
                rb_set_black(parent);
                rb_set_black(other->rb_left);
                __rb_rotate_right(parent, root);
                node = root->rb_node;
                break;
            }
        }
    }
    if (node)
        rb_set_black(node);
}