1. 程式人生 > 實用技巧 >紅黑樹原理與實現

紅黑樹原理與實現

紅黑樹

紅黑樹的性質

  1. 根節點必須是黑色

  2. 每個結點必須是黑色或者紅色

  3. 葉子節點 (nil) 是黑色

  4. 如果一個結點是紅色,則它的兩個子節點都是黑色的

  5. 從根結點出發到所有葉節點的路徑上,黑色節點數量相同

    #define K(n) ((n)->key)
    #define C(n) ((n)->color)
    #define L(n) ((n)->lchild)
    #define R(n) ((n)->rchild)
    
    //定義紅黑樹結點
    typedef struct node {
        int key;
        int color; //紅色0 黑色1 雙重黑2
        struct node *lchild, rchild;
    } node;
    
    //定義nil結點
    node __nil;
    #define nil (&__nil)
    __attribute__((constructor))
    void init_nil() {
        nil->key = 0;
        nil->color = 1;
        nil->lchild = nil->rchild = nil;
    }
    
    //建立新節點
    node *getNewNode(int key) {
        node *p = (node *)malloc(sizeof(node));
        p->key = key;
        p->lchild = p->rchild = nil;
        p->color = 0;		//預設插入紅色結點
        return p;
    }
    
    //紅黑樹的刪除
    void clear(node *root) {
        if (root == nil) return;
        clear(root->lchild);
        clear(root->rchild);
        free(root);
        return;
    }
    
    //輔助函式
    //是否有紅色孩子結點
    int hasRed(node *root) {
        return C(L(root)) == 0 || C(R(root)) == 0;
    }
    
    //找到前驅節點
    node *predecessor(node *root) {
        node *p = root->lchild;
        while (p->rchild != nil) p = p->rchild;
        return p;
    }
    

紅黑樹的調整策略

  1. 插入調整站在祖父結點
  2. 刪除調整站在父結點
  3. 插入和刪除的情況處理一共五種

紅黑樹結點的插入

情況一

兩個孩子結點均為紅色,孩子的孩子結點有紅色。把孩子改為黑色,自己改為紅色(所謂的紅色上頂)

情況二

LL型調整先進行大右旋,然後有兩種變色方案(只需要保證這兩層的黑色結點為一就可以),上黑下紅(原來的結點變為黑色,父結點變為紅色),或者上紅下黑(左孩子結點變為黑色)。

LR型調整先進行區域性小左旋,然後就變為LL型的情況。

​ RR型和RL型類似於LL型與LR型,不多贅述。

//左旋
node *left_rot(node *root) {
    node *p = root->rchild;
    root->rchild = p->lchild;
    p->lchild = root;
    return p;
}
//右旋
node *right_rot(node *root) {
    node *p = root->lchild;
    root->lchild = p->rchild;
    p->rchild = root;
    return p;
}

node *insert_maintain(node *root) {
    if (!hasRed(root)) return root; //不可能出現雙紅
    int flag = 0;
    if (C(L(root)) == 0 && hasRed(L(root))) flag = 1;
    else if (C(R(root)) == 0 && hasRed(R(root))) flag = 2; 
    if (!flag) return root;
    if (flag == 1 && C(R(root)) == 1) {		//第二種情況
        if (C(R(L(root)))) == 0) {
            root->lchild = left_rot(root->lchild);
        }
        root = right_rot(root);
    } else {
        if (C(L(R(root)))) == 0) {
            root->rchild = right_rot(root->rchild);
        }
        root = left_rot(root);
    }
    C(root) = 0;		//這裡採用紅色上頂方案,兩種情況最終變色方案一樣
    C(L(root)) = C(R(root)) = 1;
    return root;
}

node *__insert(node *root, int key) {
    if (root == nil) return getNewNode(key); //建立新節點
    if (root->key == key) return root; 
    if (root->key > key) L(root) = __insert(L(root), key); //在左子樹中插入key
    else R(root) = __insert(R(root), key);
    return insert_maintain(root); //進行插入調整
}

node *insert(node *root, int key) {
    __insert(root, key);
    C(root) = 1;		//插入之後根節點變為黑色,保證根節點為黑色,否則可能為紅色
    return root;
}

一個栗子

紅黑樹結點的刪除

刪除的結點度為1

由於紅黑樹的性質,從任意節點到葉子結點經過的黑色節點數目相同,可以得知,度為1的結點一定是黑色結點。如果一個紅色結點的度為1,那麼它的孩子一定是黑色結點,這樣就不符合紅黑樹的性質。

刪除的結點度為0

第一種情況:直接刪除紅色結點即可。
第二種情況:刪除x結點會造成紅黑樹的不平衡,這時候引入雙重黑的概念,在nil結點上增加一層黑色,相當於兩個黑結點,然後再調整雙重黑結點即可。

雙重黑結點的刪除

情況一

雙重黑結點的兄弟結點為黑色,兄弟節點的孩子全為黑色。這時候把兄弟節點和自己黑色減一,父結點黑色加一即可。

情況二

RR型,先對38結點進行左旋,把28顏色改為正常,這時候由於48結點的顏色不確定,需要把38設定為黑色,72設定為黑色,51設定為38的顏色。

情況三

RL型,對72結點進行右旋,72變為紅色,51變為黑色,然後按照情況二處理

情況四

雙重黑結點兄弟結點為紅色時,左孩子為紅色則右旋,右孩子為紅色則左旋,原來的根節點變為紅色,旋轉後的根節點變為黑色。然後進入相應的子樹中處理二重黑結點。(假裝這裡有圖)

node *erase_maintain(root) {
    if (C(L(root)) != 2 && C(R(root)) != 2) return root;
    if (hasRed(root)) {		//情況四
        int flag = 0;
        root->color = 0;
        if (C(L(root)) == 0) root = right_rot(root), flag = 1;
        else if (C(R(root)) == 0) root = left_rot(root), flag = 2;
        root->color = 1;
        if (flag == 1) root->rchild = erase_maintain(root->rchild);
        else root->lchild = erase_maintain(root->lchild);
        return root;
    }
    if (C(L(root)) == 1) {
        C(R(root)) = 1;
        if (!hasRed(L(root))) {
            C(root) += 1;
            C(L(root)) -= 1;
            return root;
        }
        if (C(L(L(root))) != 0) {
            C(L(root)) = 0;
            root->lchild = left_rot(root->lchild);
            C(L(root)) = 1;
        }
        C(L(root)) = C(root);
        root = right_rot(root);
        C(L(root)) = 1;
        C(R(root)) = 1;
    } else {
        C(L(root)) = 1;
        if (!hasRed(R(root))) {
            root->color += 1;
            C(R(root)) -= 1;
            return root;
        }
        if (C(R(R(root))) != 0) {
            C(R(root)) = 0;
            root->rchild = right_rot(root->rchild);
            C(R(root)) = 1;
        }
        C(R(root)) = C(root);
        root = left_rot(root);
        C(L(root)) = 1;
        C(R(root)) = 1;
    }
    return root;
}

node *__erase(node *root, int key) {
	if (root == nil) return root;
    if (root->key > key) root->lchild = __erase(root, key);
    else if (root->key < key) root->rchild = __erase(root, key);
    else {
        if (root->lchild == nil || root->rchild == nil) {
        	node *p = root->lchild == nil ? root->rchild : root->rchild;
            p->color += root->color;	//here
            free(root);
            return p;
        } else {
            node *p = decessor(root);
            root->key = p->key;
            root->lchild = __erase(root->lchild, p->key);
        }
    }
    return erase_maintain(root);
}

node *erase(node *root, int key) {
    root = __erase(root, key);
    root->color = 1;
    return root;
}

總結