資料結構與算法三 紅黑樹和二叉樹
- 紅黑樹
-
- 定義和性質
- 應用
- 紅黑樹的操作
-
- 建立代碼
- 左旋
-
- 代碼實作:
- 右旋
-
- 代碼實作
- 添加
- 删除
- 二叉排序樹
-
- 性質
- 優點
- 缺點
- 實作代碼
紅黑樹
定義和性質
R-B Tree,全稱是Red-Black Tree,又稱為“紅黑樹”,它一種自平衡的二叉查找樹。紅黑樹的每個節點上都有存儲位表示節點的顔色,可以是紅(Red)或黑(Black)。
性質:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN2XjlGcjAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL4VkeNdXTU1EMNpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL1EDN4MTNyUTM3ETMwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
應用
用途
運用紅黑樹主要用其兩個性質:
A.通過鍵值對方式存儲資訊
運用:
1、多個用戶端與伺服器建立連結時,此時連結為socket實質就是一個int型數字。但是每個用戶端都有自己的id,此時socket與用戶端id有個映射關系。若有大量用戶端連結時,我們就可采用紅黑樹的key-value(socket,id)方式來存儲這個資訊。以便後期查找socket與id之間的互相查找;
2、記憶體中使用:
整塊記憶體還沒有沒配時,mallo配置設定記憶體時将其假如紅黑樹中,後期free時就從紅黑樹找對應的塊将其釋放;
B.使用其中序周遊的特點(按順序存儲)
1、程序的排程:
有些程序處于等待狀态(在某個時刻會運作),存入紅黑樹中。當在某個時刻準備運作時,利用紅黑樹的中序周遊,将其小于此時間段的程序拿取出來,準備執行。
紅黑樹的操作
建立代碼
typedef int KEY_TYPE;
typedef struct _rbtree_node {
//此為紅黑樹的性質
unsigned char color;
struct _rbtree_node *right;
struct _rbtree_node *left;
struct _rbtree_node *parent;
KEY_TYPE key;
void *value;
} rbtree_node;
typedef struct _rbtree {
rbtree_node *root;
rbtree_node *nil; //NULL 所有的葉子結點都指向他
} rbtree;
左旋
左旋和右旋都是為了調節二叉樹平衡而存在的。
以x為節點進行左旋
z
x /
/ \ --(左旋)--> x
y z /
y
對x進行左旋,意味着,将“x的右孩子”設為“x的父親節點”;即,将 x變成了一個左節點(x成了為z的左孩子)!。 是以,左旋中的“左”,意味着“被旋轉的節點将變成一個左節點”。
對x進行左旋,意味着"将x變成一個左節點"。
代碼實作:
//左旋
void rbtree_left_rotate(rbtree *T, rbtree_node *x) {
rbtree_node *y = x->right; // x --> y , y --> x, right --> left, left --> right
//假設的前提:x的右孩子為y
x->right = y->left; // 将 “y的左孩子” 設為 “x的右孩子”,即 将β設為x的右孩子
if (y->left != T->nil) { // 将 “x” 設為 “y的左孩子的父親”,即 将β的父親設為x
y->left->parent = x;
}
y->parent = x->parent; //将 “x的父親” 設為 “y的父親”
if (x->parent == T->nil) { //若x父結點為空結點,将y設為根節點
T->root = y;
} else if (x == x->parent->left) { //若x為其父結點的左孩子,則将Y設為“x父結點的左孩子”
x->parent->left = y;
} else { //(若x為其父結點的右孩子)則将y設為“x父結點的右孩子””
x->parent->right = y;
}
y->left = x; //将x設為y的左孩子
x->parent = y; //将x的父結點設為y
}
詳情案例:
右旋
左旋和右旋都是為了調節二叉樹平衡而存在的。
(以x為節點進行右旋)
y
x \
/ \ --(右旋)--> x
y z \
z
對x進行右旋,意味着,将“x的左孩子”設為“x的父親節點”;即,将 x變成了一個右節點(x成了為y的右孩子)! 是以,右旋中的“右”,意味着“被旋轉的節點将變成一個右節點”。
對x進行左旋,意味着"将x變成一個左節點"。
代碼實作
//右旋
void rbtree_right_rotate(rbtree *T, rbtree_node *y) {
rbtree_node *x = y->left; // 前提:這裡假設y的左孩子為x。下面開始正式操作
y->left = x->right; // 将 “x的右孩子” 設為 “y的左孩子”,即 将β設為y的左孩子
if (x->right != T->nil) {
x->right->parent = y; // 将 “y” 設為 “x的右孩子的父親”,即 将β的父親設為y
}
x->parent = y->parent; // 将 “y的父親” 設為 “x的父親”
if (y->parent == T->nil) {
T->root = x; // 情況1:如果 “y的父親” 是空節點,則将x設為根節點
} else if (y == y->parent->right) {
y->parent->right = x; // 情況2:如果 y是它父節點的右孩子,則将x設為“y的父節點的左孩子”
} else {
y->parent->left = x; // 情況3:(y是它父節點的左孩子) 将x設為“y的父節點的左孩子”
}
x->right = y; // 将 “y” 設為 “x的右孩子”
y->parent = x; // 将 “y的父節點” 設為 “x”
}
詳情案例:
仔細觀察上面"左旋"和"右旋"的示意圖。我們能清晰的發現,它們是對稱的。無論是左旋還是右旋,被旋轉的樹,在旋轉前是二叉查找樹,并且旋轉之後仍然是一顆二叉查找樹。
添加
為了滿足紅黑樹的5條性質,一般插入結點時,将結點設定為紅色,這樣不會改變黑色的高度,這樣更容易滿足性質。
若目前結點為紅色,待插入的位置其父結點也為紅色,就要進行調整,recolor。
需要調整分為三種情況:
- (Case 1)叔叔是紅色
1.1 現象說明
目前節點(即,被插入節點)的父節點是紅色,且目前節點的祖父節點的另一個子節點(叔叔節點)也是紅色。
1.2 處理政策
(01) 将“父節點”設為黑色。
(02) 将“叔叔節點”設為黑色。
(03) 将“祖父節點”設為“紅色”。
(04) 将“祖父節點”設為“目前節點”(紅色節點);即,之後繼續對“目前節點”進行操作。
2. (Case 2)叔叔是黑色,且目前節點是右孩子
2.1 現象說明
目前節點(即,被插入節點)的父節點是紅色,叔叔節點是黑色,且目前節點是其父節點的右孩子
2.2 處理政策
(01) 将“父節點”作為“新的目前節點”。
(02) 以“新的目前節點”為支點進行左旋。
3. (Case 3)叔叔是黑色,且目前節點是左孩子
3.1 現象說明
目前節點(即,被插入節點)的父節點是紅色,叔叔節點是黑色,且目前節點是其父節點的左孩子
3.2 處理政策
(01) 将“父節點”設為“黑色”。
(02) 将“祖父節點”設為“紅色”。
(03) 以“祖父節點”為支點進行右旋。
void rbtree_insert(rbtree *T, rbtree_node *z) {
//紅黑樹隻能在葉子節點插入
rbtree_node *y = T->nil;
rbtree_node *x = T->root;
while (x != T->nil) { //查找待插入的位置
y = x;
if (z->key < x->key) {
x = x->left;
} else if (z->key > x->key) {
x = x->right;
} else { //Exist
return ;
}
}
z->parent = y; //
if (y == T->nil) { //将z結點插入其中
T->root = z;
} else if (z->key < y->key) {
y->left = z;
} else {
y->right = z;
}
z->left = T->nil;
z->right = T->nil;
z->color = RED; //新結點置成紅色
rbtree_insert_fixup(T, z); //調整結點
}
//調整樹中的z結點
void rbtree_insert_fixup(rbtree *T, rbtree_node *z) {
//樹的層級很多,用循環始終保持疊代 若z的父結點為紅色紀要一直疊代
while (z->parent->color == RED) { //z ---> RED
if (z->parent == z->parent->parent->left) { //若parent為祖父節點的左子樹,便于區分左旋右旋
rbtree_node *y = z->parent->parent->right; //擷取z的叔父結點
if (y->color == RED) {//情況1:若叔父結點為紅色,(若z的父節點和叔父結點都為紅色,則祖父結點一定為黑色,紅黑樹性質決定的)
z->parent->color = BLACK; //将父結點/叔父結點置黑色
y->color = BLACK;
z->parent->parent->color = RED; //祖父結點置紅
z = z->parent->parent; //z --> RED //注意:此為疊代的關鍵:z指向了其祖父節點,後期對祖父節點進行疊代
} else { //情況2: 若叔父結點為黑色,
if (z == z->parent->right) { //且z為父結點的右孩子(實質:祖父節點到叔父結點的黑高大于祖父節點到z結點的黑高)
z = z->parent; //
rbtree_left_rotate(T, z); //左旋,
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
rbtree_right_rotate(T, z->parent->parent);
}
}else {
rbtree_node *y = z->parent->parent->left;
if (y->color == RED) {
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent; //z --> RED
} else {
if (z == z->parent->left) {
z = z->parent;
rbtree_right_rotate(T, z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
rbtree_left_rotate(T, z->parent->parent);
}
}
}
T->root->color = BLACK;
}
删除
二叉排序樹
性質
二叉查找樹(BST)具備什麼特性呢?
1.左子樹上所有結點的值均小于或等于它的根結點的值。
2.右子樹上所有結點的值均大于或等于它的根結點的值。
3.左、右子樹也分别為二叉排序樹。
優點
利用二分查找的思想,使得查找所需的最大次數等于二叉查找樹的高度。插入結點時,也是一層一層的比較大小,找到新結點适合插入的位置。
缺點
當插入的值幾乎都比根節點小/大時,此時的二叉樹就類似與連結清單的結構,這樣效率就大打折扣。
為了解決 :二叉查找樹多次插入結點可能導緻不平衡的問題 ,就建立了自平衡的二叉查找樹,也就是紅黑樹。
實作代碼
建立二叉樹:
typedef int KEY_VALUE;
struct bstree_node {
KEY_VALUE data;
struct bstree_node *left;
struct bstree_node *right;
};
struct bstree {
struct bstree_node *root;
};
struct bstree_node *bstree_create_node(KEY_VALUE key) {
struct bstree_node *node = (struct bstree_node*)malloc(sizeof(struct bstree_node));
if (node == NULL) {
assert(0);
}
node->data = key;
node->left = node->right = NULL;
return node;
}
二叉樹的插入:
int bstree_insert(struct bstree *T, int key) {
assert(T != NULL);
if (T->root == NULL) {//若為空樹,直接指派
T->root = bstree_create_node(key);
return 0;
}
struct bstree_node *node = T->root;
struct bstree_node *tmp = T->root;
//查找該插入的位置
while (node != NULL) { //直到Node為底層了
tmp = node;
if (key < node->data) {
node = node->left;
} else {
node = node->right;
}
}
if (key < tmp->data) {
tmp->left = bstree_create_node(key);
} else {
tmp->right = bstree_create_node(key);
}
return 0;
}
二叉樹的中序周遊:
int bstree_traversal(struct bstree_node *node) {
if (node == NULL) return 0;
bstree_traversal(node->left);
printf("%4d ", node->data);
bstree_traversal(node->right);
}
紅黑樹的定義:
參考資料:
參考資料一
參考資料二