红黑树总结

红黑树

使用红黑树的5个条件

条件

  1. 根节点必须是黑色的。
  2. 所有路径上黑色节点的数量和要相等。
  3. 两个红色节点不能连在一起。
  4. 所有叶子节点(NIL)必须是黑色的。
  5. 每个节点非黑即红
对应代码段(初始化NIL结点)
/*
	初始化NIL,NIL结点是虚拟节点,本身并不存在这种结点,他的出现只是为了更好的进行删除和插入操作。
*/

void init_NIL() {
	NIL = (Node*)calloc(sizeof(Node), 1);
	NIL->color = black;
	NIL->lchild = NIL->rchild = NIL;
	NIL->data = -1;
}

调整策略

  1. 插入调整站在祖父节点往下看。
  2. 删除操作站在夫节点往下看。

必须保证插入节点后的子树的所有路径的黑色节点数量一致,否则可能会导致红黑树的性质5得不到满足。

首先是进行插入操作,插入操作的进本代码为:

Node* insert(Node* root, int data) {
	root = __insert(root, data);
	printf("change the root to black!\n");
	root->color = black;
	printf("--------------------\n");
	out_put(root);
	printf("--------------------\n");
	return root;
}

Node* __insert(Node* root, int data) {
	if (!root)return get_node(data);
	if (data < root->data) {
		root->lchild = __insert(root->lchild, data);
	}
	else if (data > root->data) {
		root->rchild = __insert(root->rchild, data);
	}
	else {
		return root;
	}
    
    
    //接下来是对插入后的树进行调整操作。
	maintain_insert(root);
}

接下来对插入进行调整

注意:一定要记住,我的所有调整操作的评判标准都是建立在5项基本原则之上的

情况一

  1. 这种情况就是当前根节点root的左右孩子都是红色节点,面对这种情况,就直接使用红色上浮,即让根节点变成红色,两个孩子节点变成黑色。如果这个根节点是最终根节点,就直接在让这个根节点变成黑色的,就是三个黑色节点。
  2. 反之,如果没有红色孩子节点就可以直接返回这个结点没必要调整了。
代码演示
if (root->lchild->color == red && root->rchild->color == red) {
		root->lchild->color = black;
		root->rchild->color = black;
		root->color = red;
		return root;
	}

情况2

首先判断是不是应该进行调整操作

这个情况的特点是ungle节点是黑色的。

判断:

  1. 针对情况二,我先判断是不是以下情况:

    1.

    代码演示:

    if ((root->lchild->color == red && !has_red_kid(root->lchild)) || (root->lchild->color == red
    		&& !has_red_kid(root->lchild))) {
    		return root;
    	}
    
    1. 如果兄弟节点没有红色孩子,那么可以直接返回。

      if (!has_red_kid(root))return root;
      

    在这之前,我要写好二叉树的左旋和右旋操作。以便等会可以直接调用!!!

    左旋

    Node* left_rotate(Node* root) {
    	Node* temp = root;
    	Node* node = temp->rchild;
    	temp->rchild = node->lchild;
    	node->lchild = temp;
    	return node;
    }
    
    

    右旋

    Node* right_rotate(Node* root) {
    	Node* temp = root;
    	Node* node = temp->lchild;
    	temp->lchild = node->rchild;
    	node->rchild = temp;
    	return node;
    }
    

    剩下的所有情况都是不符合5的基本原则的。

    接下来开始进行调整操作

  2. 判断完这些,就是对这种情况下时对这棵树操作了,首先判断它的红色节点的孩子节点的哪个孩子是红色的,就像下面的这种情况:假设root的左孩子是红色节点,这个红色节点的左边节点是红色,那么就是LL情况,否则就是LR情况。所以总共就有四种情况。先对失衡位置进行调整,调整完毕后一般会变成情况1,再根据情况一进行调整就行了。

    例如原来的树是这样的:

首先进行右旋调整

代码演示:

if (has_red_kid(root->lchild)) {
		flag = 1;
		if (root->lchild->rchild->color == red) {
			flag = 2;
			root->lchild = left_rotate(root->lchild);
		}
		root = right_rotate(root);
	}
	else {
		flag = 3;
		if (root->rchild->lchild->color == red) {
			root->rchild = right_rotate(root->rchild);
			flag = 4;
		}
		root = left_rotate(root);
	}

接下来按情况一进行调整变成

调整称为正常情况。

插入操作的最终代码演示

c语言版:插入操作


#include <stdio.h>
#include <stdlib.h>
#include <time.h>

typedef struct Node {
    int key;
    int color;// 0 red, 1 black, 2 double black
    struct Node *lchild, *rchild;
} Node;

Node __NIL;
#define NIL (&__NIL)
__attribute__((constructor))
void init_NIL() {
    NIL->key = -1;
    NIL->color = 1;
    NIL->lchild = NIL->rchild = NIL;
    return ;
}

Node *getNewNode(int key) {
    Node *p = (Node *)malloc(sizeof(Node));
    p->key = key;
    p->lchild = p->rchild = NIL;
    p->color = 0;
    return p;
}

int has_red_child(Node *root) {
    return root->lchild->color == 0 || root->rchild->color == 0;
}

Node *left_rotate(Node *root) {
    printf("left rotate : %d\n", root->key);
    Node *temp = root->rchild;
    root->rchild = temp->lchild;
    temp->lchild = root;
    return temp;
}

Node *right_rotate(Node *root) {
    printf("right rotate : %d\n", root->key);
    Node *temp = root->lchild;
    root->lchild = temp->rchild;
    temp->rchild = root;
    return temp;
}

const char *insert_maintain_str[] = {"", "LL", "LR", "RR", "RL"};

Node *insert_maintain(Node *root) {
    if (!has_red_child(root)) return root;
    if (root->lchild->color == 0 && root->rchild->color == 0) {
        //情况1
        printf("Situation 1 : change color\n");
        root->color = 0;
        root->lchild->color = 1;
        root->rchild->color = 1;
        return root;
    }
    if (root->lchild->color == 0 && !has_red_child(root->lchild)) return root;
    if (root->rchild->color == 0 && !has_red_child(root->rchild)) return root;
    //情况2
    int flag = 0;
    printf("Situation 2: \n");
    if (root->lchild->color == 0) {
        if (root->lchild->rchild->color == 0) {
            root->lchild = left_rotate(root->lchild);
            flag = 2;
        } else {
            flag = 1;
        }
        root = right_rotate(root);
    } else {
        if (root->rchild->lchild->color == 0) {
            root->rchild = right_rotate(root->rchild);
            flag = 3;
        } else {
            flag = 4;
        }
        root = left_rotate(root);
    }
    printf("Maintain Type : %s\n", insert_maintain_str[flag]);
    printf("Situation 2 : change color\n");
    root->color = 0;
    root->lchild->color = 1;
    root->rchild->color = 1;
    return root;
}

Node *__insert(Node *root, int key) {
    if (root == NIL) return getNewNode(key);
    if (root->key == key) return root;
    if (key < root->key) {
        root->lchild = __insert(root->lchild, key);
    } else {
        root->rchild = __insert(root->rchild, key);
    }
    return insert_maintain(root);
}

Node *insert(Node *root, int key) {
    root = __insert(root, key);
    root->color = 1;
    printf("change root color to black!\n");
    return root;
}

void clear(Node *root) {
    if (root == NIL) return ;
    clear(root->lchild);
    clear(root->rchild);
    free(root);
    return ;
}

void print(Node *root) {
    printf("(%d | %d, %d, %d)\n",
          root->color, root->key,
          root->lchild->key,
          root->rchild->key
    );
    return ;
}

void output(Node *root) {
    if (root == NIL) return ;
    print(root);
    output(root->lchild);
    output(root->rchild);
    return ;
}

Node *rand_insert(Node *root) {
    int val = rand() % 100;
    printf("\ninsert %d to RB_Tree : \n", val);
    root = insert(root, val);
    printf("-----------------\n");
    output(root);
    printf("-----------------\n");
    return root;
}

int main() {
    int n;
    srand(time(0));
    scanf("%d", &n);
    Node *root = NIL;
    for (int i = 0; i < n; i++) {
        root = rand_insert(root);
    }
    clear(root);
    return 0;
}

代码输出结果为:

红黑树的删除操作

二重黑

为什么要引入二重黑(doube_black)的概念呢?

答:首先,为什么要引入二重黑概念呢?
虚拟节点NIL是一个而二重黑节点,这是为了在删除操作完成后,保证所有路径上的黑色节点个数都相等的情况。

  • 例如你要删除一个黑色节点,那么这条路径上得黑色结点个数必定少一个,所以就必须给其中得一个黑色节点变成二重黑或者让一个红色结点变成黑色结点。
  1. 假设你要删除某一个叶子节点,那么这个节点所在的路径的所有黑色节点个数就要减去一个,那么如果引入了二重黑的概念,此时就可以让一个二重黑节点去占用空出来的这个节点,然后再让这个二重黑节点消去一重黑变成一重黑节点,那么此时的所有路径上的黑色节点个数就都是相等的了。

  2. 假设你要删除一个黑色的根节点,那么删去的时候就会有一个节点顶上来,并且为了保证删除的的这条路径上的黑色节点的个数是不变的,所以这个节点要加上删除节点的那一层黑,如果这个节点本身就是一个黑色节点,那么加上这一层黑之后就会变成二重黑节点,那么这也是二重黑节点保证红黑树性质的其中一种情况。

    首先是删除结点得代码演示:

    Node* erase(Node* root, int data) {
    	root = __erase(root, data);
    	printf("change the root black\n");
    	root->color = black;
    	printf("------------------------\n");
    	out_put(root);
    	printf("------------------------\n");
    	return root;
    }
    
    Node* __erase(Node* root, int data) {
    	if (root == NIL)return root;
    	if (root->data > data)root->rchild = __erase(root->rchild, data);
    	else if (data < root->color)root->lchild = __erase(root->lchild, data);
    	else {
    		if (root->lchild == NIL || root->rchild == NIL) {
    			Node* temp;
    			if (root->lchild == NIL)temp = root->rchild;
    			else temp = root->lchild;
                //这是保证每条路径的黑色结点数量保存不变,但这也是产生double_black的关键代码句。
    			temp->color += root->color;
    			free(root);
    			return temp;
    		}
    		else {
    			Node* temp = predecssor(root->lchild);
    			root->data = temp->data;
    			root->lchild = __erase(root->lchild, temp->data);
    		}
    	}
    	return erase_maintain(root);
    }
    

红黑树的调整分为以下几种情况:

情况一:

特征
  • double_black的兄弟节点是red.
调整策略(假设左节点是红色的)

假设情况如下图:

  1. 将根节点制成红色。(这么做的目的是等下我要将根节点进行大右旋,这样会导致右子树多一个黑节点,但是为了保持所有路径上的黑色节点数量保持不变,所以我要将根节点先制成红色)

  2. 进行大右旋。(右旋和左旋主要看红色结点在左子树还是右子树,在左子树就左旋,在右子树就右旋)

  3. 将根节点制成黑色。因为刚才我将旧的根节点制成红色,导致这颗子树上的所有路径的黑色节点数量减一,所以这个新的根节点要变成黑色。

    代码演示

    if (has_red_kid(root)) {
    		printf("the brother is red\n");
    		root->color = red;
    		int flag = 0;
    		if (root->lchild->color == red) {
    			root = right_rotate(root);
    		}
    		else {
    			root = left_rotate(root);
    			flag = 1;
    		}
    		root->color = black;
    		if (flag)root->lchild = erase_maintain(root->lchild);
    		else {
    			root->rchild = erase_maintain(root->rchild);
    		}
    		return root;
    	}
    

情况二:

特征:
  • 要删除的节点的兄弟节点的孩子节点全部为黑色节点.

操作将要删除的这个节点用一个NIL节点去取代,并且将根节点的黑度+1,将兄弟节点的黑度+1,将NIL节点的黑度-1,这样所有路径上的黑色节点的个数就是不变的了。

删除后的树为

此时将根节点的黑度加一,左右子树的黑色减一,这样做的目的是使得子树的double_black结点恢复正常,同时让根节点变成double_black,此时你可能会问:“为什么要牺牲根节点让它变成double_black让孩子节点恢复正常?”,因为我在最后会对最终根节点由double_black变成black,这样所有根节点就会恢复正常了。

调整后的树为:
所有路径上的黑色节点个数是3个,double_black节点算两个

此时可以看到根节点变成double_blakc了,但是这个子树的所有路径上的黑色结点都还是两个,所以可以说是所有路径上的黑色节点数量保存不变。

最后我再让根节点的double_black强行变成black
代码演示

if (root->lchild->color == double_black && !has_red_kid(root->rchild) ||
		root->rchild->color == double_black && !has_red_kid(root->lchild)) {
		printf("the sitution 1\n");
		root->lchild->color--;
		root->rchild->color--;
		root->color++;
		return root;
	}

情况三:

接下来是RL(LR),RR(LL)情况的具体操作过程

  • 如果该兄弟节点的左孩子和右孩子都是红色节点时,优先考虑使用大左旋或则大右旋,即优先度级别是:RR>RL,LL>LR。当然如果是左兄弟的左孩子或者右兄弟的右孩子,当然就直接大右旋或则大左旋。

  • 接下来是RL(LR)情况,首先要将它变成RR情况或则LL情况,再进行大旋转。例如

首先将51节点改成红色,然后来一个小右旋将51调下去。得到

再将48节点改成黑色,就变成了标准的RR情况

接下来进行RR情况下的调整即可:(注意一下的操作只对标准的RR(LL)情况适用,所以进行下面的调整之前一定要确定前面的操作是完整并且正确的)

​ 所以经过小左旋或者小右旋调整后的情况:

  • 如下图,如果经过小左旋后的情况是下面的这种,就是RR情况。对于下面的图,我就要采用大左旋

    经过大左旋调整,调整后的情况如下:

    1. 51取代了38的位置,占据了中枢节点。
    2. 因为不知道节点48的颜色,所以一定要将38的节点改成黑色,以免发生冲突.
    3. 这条路径上已经有两个黑色节点(上面还没转换时这条路径上有一个双重黑节点,所以也算是两个黑色节点)为了保存更改前后这条路径上黑色节点个数不变,所以要将节点51改成红色。(发现51的颜色和转换之前的根节点32的颜色是相等的)
    4. 将节点72由红色改成黑色,因为他本来是红色的,为了保证不出现红色和红色相连的情况,所以要将他改成黑色的。
    5. 如果38节点原本是黑色的,那么51节点的颜色要改成黑色,因为28号结点减去一度黑后这条路径上的黑色节点个数就少了一个,而为了保存黑色节点个数保持不变,所以要将新插入到这条路径上的51号节点改成黑色,同时节点72号也是一定要改成黑色的
    6. 为什么72号节点一定要改成黑色,因为原本51号节点是单属于它们路径上的一个黑色节点,现在这个黑色节点被放在它们的根部位置,相当于贡献出去了,所以为了保持黑色节点数量相等,所以必须改成黑色的。

    可以理解我调整的目的就是使得brother的孩子节点变成黑色的,因为我调整的原因就是ungle节点的孩子

    节点的红色的,因此我们调整之后要记得将这个孩子节点变成黑色。

    我认为上一步操作的关键是:

    1. 51号结点一定要改成38号结点的颜色。
    2. 记得将72号结点改成黑色(就是你刚才发现是红色的那个孩子节点)。

代码演示

int flag = 0;
	if (root->lchild->color == double_black) {
		flag = 1;
		root->lchild->color = black;
		if (root->rchild->rchild->color != red) {
			flag = 2;
			root->rchild->color = red;
			root->rchild = right_rotate(root->rchild);
			root->rchild->color = black;
		}
		root = left_rotate(root);
		root->color = root->lchild->color;
	}
	else {
		flag = 3;
		root->rchild->color = black;
		if (root->lchild->lchild->color != red) {
			flag = 4;
			root->lchild->color = red;
			root->lchild = left_rotate(root->lchild);
			root->lchild->color = black;
		}
		root = right_rotate(root);
		root->color = root->rchild->color;
	}
	root->lchild->color = root->rchild->color = black;
	printf("maintain type is %s\n", maintain_str[flag]);
	return root;

总的代码演示

/*************************************************************************
	> File Name: 9.RB_tree.cpp
	> Author: 
	> Mail: 
	> Created Time: 
 ************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

typedef struct Node {
    int key;
    int color;// 0 red, 1 black, 2 double black
    struct Node *lchild, *rchild;
} Node;

Node __NIL;
#define NIL (&__NIL)
__attribute__((constructor))
void init_NIL() {
    NIL->key = -1;
    NIL->color = 1;
    NIL->lchild = NIL->rchild = NIL;
    return ;
}

Node *getNewNode(int key) {
    Node *p = (Node *)malloc(sizeof(Node));
    p->key = key;
    p->lchild = p->rchild = NIL;
    p->color = 0;
    return p;
}

int has_red_child(Node *root) {
    return root->lchild->color == 0 || root->rchild->color == 0;
}

Node *left_rotate(Node *root) {
    printf("left rotate : %d\n", root->key);
    Node *temp = root->rchild;
    root->rchild = temp->lchild;
    temp->lchild = root;
    return temp;
}

Node *right_rotate(Node *root) {
    printf("right rotate : %d\n", root->key);
    Node *temp = root->lchild;
    root->lchild = temp->rchild;
    temp->rchild = root;
    return temp;
}

const char *maintain_str[] = {"", "LL", "LR", "RR", "RL"};

Node *insert_maintain(Node *root) {
    if (!has_red_child(root)) return root;
    if (root->lchild->color == 0 && root->rchild->color == 0) {
        //情况1
        printf("Situation 1 : change color\n");
        root->color = 0;
        root->lchild->color = 1;
        root->rchild->color = 1;
        return root;
    }
    if (root->lchild->color == 0 && !has_red_child(root->lchild)) return root;
    if (root->rchild->color == 0 && !has_red_child(root->rchild)) return root;
    //情况2
    int flag = 0;
    printf("Situation 2: \n");
    if (root->lchild->color == 0) {
        if (root->lchild->rchild->color == 0) {
            root->lchild = left_rotate(root->lchild);
            flag = 2;
        } else {
            flag = 1;
        }
        root = right_rotate(root);
    } else {
        if (root->rchild->lchild->color == 0) {
            root->rchild = right_rotate(root->rchild);
            flag = 3;
        } else {
            flag = 4;
        }
        root = left_rotate(root);
    }
    printf("Maintain Type : %s\n", maintain_str[flag]);
    printf("Situation 2 : change color\n");
    root->color = 0;
    root->lchild->color = 1;
    root->rchild->color = 1;
    return root;
}

Node *__insert(Node *root, int key) {
    if (root == NIL) return getNewNode(key);
    if (root->key == key) return root;
    if (key < root->key) {
        root->lchild = __insert(root->lchild, key);
    } else {
        root->rchild = __insert(root->rchild, key);
    }
    return insert_maintain(root);
}

Node *insert(Node *root, int key) {
    root = __insert(root, key);
    root->color = 1;
    printf("change root color to black!\n");
    return root;
}

Node *predecessor(Node *root) {
    Node *temp = root->lchild;
    while (temp->rchild != NIL) temp = temp->rchild;
    return temp;
}

Node *erase_maintain(Node *root) {
    if (root->lchild->color != 2 && root->rchild->color != 2) return root;
    if (has_red_child(root)) {
        printf("Situation X : brother is red!\n");
        int flag = 0;
        root->color = 0;
        if (root->lchild->color == 0) {
            root = right_rotate(root); flag = 0;
        } else {
            root = left_rotate(root); flag = 1;
        }
        root->color = 1;
        if (flag == 0) root->rchild = erase_maintain(root->rchild);
        else root->lchild = erase_maintain(root->lchild);
        return root;
    }
    if ((root->lchild->color == 2 && !has_red_child(root->rchild)) || 
        (root->rchild->color == 2 && !has_red_child(root->lchild))) {
        printf("Situation 1 : change color done!\n");
        root->color += 1;
        root->lchild->color -= 1;
        root->rchild->color -= 1;
        return root;
    }
    int maintain_type = 0;
    if (root->rchild->color == 2) {// L
        root->rchild->color = 1;
        if (root->lchild->lchild->color != 0) {// R
            root->lchild->color = 0;
            root->lchild = left_rotate(root->lchild);
            root->lchild->color = 1;
            maintain_type = 2;
        } else {                                      
            maintain_type = 1;
        }
        root = right_rotate(root);
        root->color = root->rchild->color;
    } else { //R
        root->lchild->color = 1;
        if (root->rchild->rchild->color != 0) {// L
            root->rchild->color = 0;
            root->rchild = right_rotate(root->rchild);
            root->rchild->color = 1;
            maintain_type = 3;
        } else {
            maintain_type = 4;
        }
        root = left_rotate(root);
        root->color = root->lchild->color;
    }
    root->lchild->color = root->rchild->color = 1;
    printf("Maintain Type : %s\n", maintain_str[maintain_type]);
    return root;
}

Node *__erase(Node *root, int key) {
    if (root == NIL) return NIL;
    if (key < root->key) {
        root->lchild = __erase(root->lchild, key);
    } else if (key > root->key) {
        root->rchild = __erase(root->rchild, key);
    } else {
        if (root->lchild == NIL || root->rchild == NIL) {
            Node *temp = root->lchild == NIL ? root->rchild : root->lchild;
            temp->color += root->color;
            free(root);
            return temp;
        } else {
            Node *temp = predecessor(root);
            root->key = temp->key;
            root->lchild = __erase(root->lchild, temp->key);
        }
    }
    return erase_maintain(root);
}

void output(Node *);

Node *erase(Node *root, int key) {
    printf("\n earse %d from red black tree : \n", key);
    root = __erase(root, key);
    root->color = 1;
    printf("change root color to black\n");
    output(root);
    return root;
}

void clear(Node *root) {
    if (root == NIL) return ;
    clear(root->lchild);
    clear(root->rchild);
    free(root);
    return ;
}

void print(Node *root) {
    printf("(%d | %d, %d, %d)\n",
          root->color, root->key, 
          root->lchild->key,
          root->rchild->key
    );
    return ;
}

void __output(Node *root) {
    if (root == NIL) return ;
    print(root);
    __output(root->lchild);
    __output(root->rchild);
    return ;
}

void output(Node *root) {
    __output(root);
    printf("NIL : ");
    print(NIL);
    return ;
}

Node *rand_insert(Node *root) {
    int val = rand() % 100;
    printf("\ninsert %d to RB_Tree : \n", val);
    root = insert(root, val);
    printf("-----------------\n");
    output(root);
    printf("-----------------\n");
    return root;
}

int main() {
    int n;
    srand(time(0));
    scanf("%d", &n);
    Node *root = NIL;
    for (int i = 0; i < n; i++) {
        root = rand_insert(root);
    }
    while (~scanf("%d", &n)) {
        root = erase(root, n);
    }
    clear(root);
    return 0;
}


删除操作的输出为

这是原来的二叉树

接下来对这棵树进行删除操作

通过对上面红黑树的插入和操作的学习,我们可以将红黑树和AVL树进行对比:
相同点:

  1. 都是对数据进行存储和查找,并且查找时间复杂度为log(N)。
  2. 对数据的删除和查找操作中对数据的查找代码都是一样的。
  3. 两种树都应该引入虚拟节点的概念。
  4. 两种树的左旋右旋操作是一样的。
    不同点:
  5. 调整的判断方式不同:AVL树的调整判断方式是左右树高之差大于等于2,而红黑树的调整判断方式是上面所说的5个基本原则。
  6. 红黑树的调整要求比AVL树更加的低。
  7. 红黑树引入了颜色的变量,但是AVL树则是引入了树高的变量。
  8. 红黑树的代码比AVL树要复杂。

以上就是对二叉树的插入操作和删除操作的完整概述,如有错误或者其他观点欢迎指出交流。

posted @ 2021-11-15 00:02  shawchakong  阅读(61)  评论(0)    收藏  举报