Professional Documents
Culture Documents
Red Black Tree Nhóm 3
Red Black Tree Nhóm 3
ĐỀ TÀI:
Không có thành công nào mà không có sự hỗ trợ, dìu dắt dù ít hay nhiều, dù
trực tiếp hay gián tiếp của mọi người xung quanh bởi đơn giản một bàn tay không thể
vỗ thành tiếng. Với lòng biết ơn sâu sắc của mình, chúng em xin được gửi lời cảm ơn
tới toàn bộ quý thầy cô đã đồng hành cùng chúng em cũng như các thành viên, thầy cô
đã truyền đạt vốn tri thức quý giá của mình bằng lòng nhiệt thành và tâm huyết nhất
trong thời gian học tập vừa qua. Đặc biệt chúng em cũng xin cảm ơn Khoa hệ thống
thông tin quản lý định hướng Nhật Bản đã cho chúng em có cơ hội được tiếp cận với
môn học thật sự cần thiết và hữu ích, đó là môn “Cấu trúc dữ liệu và giải thuật”.
Chúng em xin được cảm ơn Cô Nguyễn Thị Giang Huyền đã luôn nhiệt huyết, tận tâm
hướng dẫn, giải đáp những khúc mắc cho chúng em cũng như các bạn trong lớp, nếu
không có những hướng dẫn chi tiết tận tình của cô thì có lẽ chúng em cũng không thể
hoàn thành bài báo cáo này.
Trong quá trình làm bài tập lớn không thể tránh được những thiếu sót, chúng
em rất mong mình có thể nhận được những ý kiến đóng góp của cô cũng như của các
bạn trong lớp để ngày một hoàn thiện bản thân mình hơn.
Sau cùng chúng em xin kính chúc các thầy cô trong khoa nói chung và Cô
Nguyễn Thị Giang Huyền nói riêng luôn mạnh khỏe, hạnh phúc để có thể tiếp tục
giảng dạy và công tác ở Học Viện, và truyền bá những kiến thức bổ ích cho chúng em
cũng như là các thế hệ sau này.
Chúng em xin được cảm ơn.
Chúng em xin được cam đoan rằng, bài tiểu luận trên là công sức và chất xám
của Nhóm 3 bỏ ra trong thời gian vừa qua. Việc một số mục có sự tham khảo về kiến
thức nghiệp vụ chung là trung thực, chính xác. Chúng em xin được chịu trách nhiệm
về lời cam đoan này cũng như đảm bảo tính liêm chính trong học thuật.
1
PHẦN I: GIỚI THIỆU
1. Đặt vấn đề
Cây tìm kiếm nhị phân là một cấu trúc lưu trữ dữ liệu tốt với tốc độ tìm kiếm
nhanh. Tuy nhiên trong một số trường hợp cây tìm kiếm nhị phân có một số hạn chế.
Hình Hình
1. Các1.node
Các node
đượcđược
chèn chèn
theo thứ
theotựthứ
giảm
tự giảm
dần dần
Những node này tự sắp xếp thành một đường không phân nhánh.
Độ phức tạp: Khi cây một nhánh, sẽ trở thành một danh sách liên kết, dữ liệu sẽ
là một chiều thay vì hai chiều. Trong trường hợp này, thời gian truy xuất giảm về
O(N), thay vì O(log2N) đối với cây cân bằng.
Để đảm bảo thời gian truy xuất nhanh của cây, chúng ta cần phải bảo đảm cây
luôn luôn cân bằng (ít ra cũng là cây gần cân bằng).
Từ đó cây nhị phân tự cân bằng ra đời và có 2 cây tự cân bằng ra đời là cây
AVL và cây đỏ đen.
Cây AVL đã là BST tự cân bằng được phát minh vào năm 1962. Vậy thì tại sao
năm 1972 lại phải tạo ra thêm cây đỏ đen (RBTree) làm gì nữa? Đúng là sự cân bằng
của cây AVL thuộc dạng hoàn hảo khi chênh lệch về chiều cao của 2 cây con trái phải
luôn luôn nhỏ hơn hoặc bằng 1. Tuy nhiên, không phải khi nào hoàn hảo cũng tốt. Khi
ta thêm rất nhiều Node vào cây thì sẽ khiến cây phải quay (cân bằng) liên tục. Cây có
càng nhiều phần tử thì việc cân bằng lại cây sẽ càng mất công hơn. Cây đỏ đen sẽ khắc
phục yếu điểm này.
Ở đây mình có một cây đỏ đen (nhìn khá cân bằng), có thể thấy từ node root
đến node NULL đầu tiên có 3 node đen là 12, 10, null. Nhưng từ node root đến node
NULL thứ ba thì chỉ có 2 node đen là 12 và null. Như vậy thì đã vi phạm quy tắc số 4.
(Theo quy tắc số 4: Mọi đường dẫn từ một node đến bất kì node NULL (thuộc con của
nó) thì đều có cùng số lượng node đen).
3
Vì vậy, không thể có 3 node đen liên tiếp. Vì thế số lượng node đen từ root đến
tất cả các node NULL luôn luôn bằng nhau. Sự khác biệt giữa các đường đi là các
node đỏ, tuy nhiên vẫn không đáng kể để ảnh hưởng đến sự cân bằng của cây.
3. Cấu trúc
enum COLOR
{
RED, BLACK
};
struct RBNode
{
int Infor;
RBNode* Left;
RBNode* Right;
RBNode* Parent;
COLOR Color;
};
Chiều cao đen: là số lượng node đen từ root → NULL. Vì thế nếu cây đỏ đen
có chiều cao là h thì có chiều cao đen >= h / 2.
Chiều cao cây đỏ đen có n node là h thì: h <= 2 * log2(n + 1).
Độ sâu đen của một node là số lượng node đen từ node đó → NULL. (Khá
tương tự chiều cao đen).
Mỗi cây đỏ đen đều là một trường hợp đặc biệt của cây tìm kiếm nhị phân BST.
4
PHẦN 2: CÁC THAO TÁC TRÊN CÂY ĐỎ ĐEN
1. Phép tìm kiếm
Thực hiện tương tự như phép tìm kiếm trong cây nhị phân theo 2 cách: Đệ quy
hoặc Không đệ quy.
Đệ quy:
RBNode *search (RBNode * root, int key)
{
if (root =null)
return null;
if (root ->info==key)
return root;
if (root->info<key)
return search (root -> right, key)
else
return search (root -> left, key);
}
Không đệ quy:
RBNode * search (RBnode * root, int key)
{
RBnode * p=root;
while (p!=null){
if (p->info==key)return p;
if (p->info<key)
p=p->right;
else
p=p->left;
}
}
2. Phép quay
Thực ra quay không có nghĩa là các node bị quay mà để chỉ sự thay đổi quan hệ
giữa chúng. Một node được chọn làm "đỉnh" của phép quay. Nếu chúng ta đang thực
hiện một phép quay qua phải, node "đỉnh" này sẽ di chuyển xuống dưới và về bên
phải, vào vị trí của node con bên phải của nó. Node con bên trái sẽ đi lên để chiếm lấy
vị trí của nó.
5
Phải đảm bảo trong phép quay phải, node ở đỉnh phải có node con trái. Nếu
không chẳng có gì để quay vào điểm đỉnh. Tương tự, nếu làm phép quay trái, node ở
đỉnh phải có node con phải.
Hình 5. Sơ đồ phả hệ
6
Hình 6. Các trường hợp thêm node
7
→ Không vi phạm định nghĩa cây đỏ đen.
→ Không vi phạm tính chất của cây đỏ đen.
→ Không cần điều chỉnh.
TH3: Cả P và U đều đỏ
Hình 9. Cả P và U đều đỏ
VD: Tạo cây đỏ đen từ dãy số sau: 50, 75, 25, 80, 100, 110, 105
TH4.1. P là con trái của G, N là con trái của P (Left – Left): P đỏ, U đen, P trái G,
N trái P
9
TH4.2. P là con phải của G, N là con phải của P(Right - Right): P đỏ, U đen, P phải
G, N phải P
TH4.3. P là con trái của G, N là con phải của P(Left - Right): P đỏ, U đen, P trái G,
N phải P
→ Vi phạm yêu cầu: cha nút đỏ phải là nút đen.
→ Quay trái node P.
10
Hình 16. Trường hợp Left–Right
TH4.4. P là con phải của G, N là con trái của P (Right - Left): P đỏ, U đen, P phải
G, N trái P
11
→ Vi phạm yêu cầu: cha nút đỏ phải là nút đen.
→ Quay phải node P.
→ Thực hiện như R - R.
→ Quay trái node G.
→ Đổi màu node G và N: N thành đen, G thành đỏ (đảm bảo chiều cao đen).
VD: Tạo cây đỏ đen từ dãy số sau: 50, 75, 25, 80, 100, 110, 105 (tiếp tục với 100)
12
4. Thao tác xóa node trên cây đỏ đen
a, Các bước tiến hành
B1: Tìm kiếm node cần xóa, nếu không tìm thấy thì dừng.
B2: Nếu tìm thấy, ta tiến hành xóa.
Node cần xóa là nút lá.
Node cần xóa là nút 1 con: Nối con trỏ trỏ vào nút cần xóa tới thẳng con
của nút cần xóa.
Node cần xóa là nút 2 con (tìm node thay thế, thường là nút lớn nhất của
con bên trái).
B3: Điều chỉnh lại nếu vi phạm các quy định của cây đỏ đen.
b, Các TH có thể xảy ra nếu xóa node X của cây đỏ đen
TH1: Node cần xóa X là node đỏ hoặc node gốc
Việc xóa node X sẽ không ảnh hưởng tới đặc điểm và tính chất của cây đỏ đen nên
không cần điều chỉnh.
Nếu xóa node bên phải thì duyệt đến khi node con bên trai là lá thì lấy lá thế chỗ
cho node xóa.
13
TH2.2. Hai con của X (kể cả node NIL) đều là node đen:
Gọi S là node anh em của X: Có 4 trường hợp xảy ra:
S màu đen và 2 con của S màu đen
S màu đen và con phải của S màu đỏ
S màu đen và con trái của S màu đỏ, con phải S màu đen
S màu đỏ
Hình 25. Hai con của X (kể cả node NIL) đều là node đen
TH2.2.2. S màu đen và con phải của S màu đỏ (con trái màu gì cũng được):
14
Quay trái tại P
Sau đó tiến hành đảo màu của P và S: P màu gì S màu đó, Z đổi màu đen
Hình 29. S màu đen và con trái của S màu đỏ, con phải S màu đen
15
Sau khi đổi màu, trở lại trường hợp trước đó: S màu đen và con phải của S màu đỏ
Quay trái tại P và tiến hành đảo màu
Hình 30. S màu đen và con trái của S màu đỏ, con phải S màu đen
Y vẫn là nút đen kép, tùy từng cấu trúc của cây xem xem nó rơi vào trường hợp nào
trong các TH rồi tiến hành tương tự.
16
VD: Xóa node gốc
Khi xóa 50, tiến hành đổi chỗ 25 và 50
17
#include <cmath>
struct Node {
int data;
Node* left;
Node* right;
Node* parent;
bool color;
//1 -> Red | 0 -> Black
};
// Trả về x
return x;
}
// Trả về y;
return y;
}
struct RedBlackTree {
Node* Root;
bool ll = false;
18
bool rr = false;
bool lr = false;
bool rl = false;
RedBlackTree() {
Root = NULL;
}
Node* insertHelp(Node* root, int key) {
// f đúng khi có xung đột RED RED
bool f = false;
if (root == NULL) {
return new Node{ key, NULL, NULL, NULL, 1 }; // RED Node
}
else if (key < root->data) {
root->left = insertHelp(root->left, key);
root->left->parent = root;
// root->left = Node X
// root = X->parent
if (Root != root) {
if (root->color == root->left->color == 1)
f = true;
}
}
else {
root->right = insertHelp(root->right, key);
root->right->parent = root;
// root->right = Node X
// root = X->parent
if (Root != root) {
if (root->color == root->right->color == 1)
f = true;
}
}
// Xử lý 4 TH lệch
// *** Khi này (ll, lr, rr, rl = false) nên chưa xử lí liền
// *** Sau khi thoát 1 vòng đệ quy thì: root = X->parent->parent
// *** Tức là Node ông, lúc này ta quay Node ông
// Case 1 : Left left - Trái trái
if (ll) {
root = rotateRight(root);// Quay phải tại nút gốc
root->color = 0; // Chuyển nút gốc thành Black
root->right->color = 1; // Đổi màu con phải nút gốc thành Red
ll = false;
}
// Case 2 : Right right - Phải phải
else if (rr) {
19
root = rotateLeft(root); // Quay phải tại nút gốc
root->color = 0; // Đổi màu nút gốc thành Black
root->left->color = 1; // Đổi màu bên trái nút gốc thành Red
rr = false;
}
// Case 3 : Left right - Phải trái
else if (lr) {
root->left = rotateLeft(root->left);// Quay trái tại nút bên trái
gốc
root->left->parent = root;
root = rotateRight(root); // Quay phải nút gốc
root->color = 0; // Đổi màu nút gốc thành Black
root->right->color = 1; // Đổi màu bên phải nút gốc thành đỏ
lr = false;
}
// Case 4 : Right left - Phải trái
else if (rl) {
root->right = rotateRight(root->right);
root->right->parent = root;
root = rotateLeft(root);
root->color = 0;
root->left->color = 1;
rl = false;
}
// In - Print 2D ra console
void print2DUtil(Node* root, int space) {
if (root == NULL)
return;
space += COUNT;
print2DUtil(root->right, space);
cout << endl;
21
print2DUtil(root->left, space);
}
int main() {
RedBlackTree RBTree;
RBTree.Insert(1);
RBTree.Insert(4);
RBTree.Insert(6);
RBTree.Insert(3);
RBTree.Insert(5);
RBTree.Insert(7);
RBTree.Insert(8);
RBTree.Insert(2);
RBTree.Insert(9);
print2D(RBTree.Root);
return 0;
}
Kết quả:
22
using namespace std;
#define COUNT 10
struct Node {
int data;
Node* left;
Node* right;
Node* parent;
bool color;
//1 -> Red | 0 -> Black
};
return current;
}
bool hasRedChild(Node* x) {
23
if (x->right != NULL)
if (x->right->color == 1)
return true;
if (x->left != NULL)
if (x->left->color == 1)
return true;
return false;
}
// In - Print 2D ra console
void print2DUtil(Node* root, int space) {
if (root == NULL)
return;
space += COUNT;
print2DUtil(root->right, space);
cout << endl;
struct RedBlackTree {
Node* Root;
bool ll = false;
bool rr = false;
bool lr = false;
bool rl = false;
RedBlackTree() {
Root = NULL;
}
Node* rotateRight(Node* root) {
Node* x = root->left;
// Gán x->right vào left root
root->left = x->right;
24
if (x->right != NULL)
x->right->parent = root;
x->parent = root->parent;
if (root->parent == NULL)
Root = x;
else if (root->parent->left == root)
root->parent->left = x;
else
root->parent->right = x;
// Gán root vào x.right
x->right = root;
root->parent = x;
// Trả về x
return x;
}
y->parent = root->parent;
if (root->parent == NULL)
Root = y;
else if (root->parent->left == root)
root->parent->left = y;
else
root->parent->right = y;
// Trả về y;
return y;
}
Node* insertHelp(Node* root, int key) {
// f đúng khi có xung đột RED RED
bool f = false;
if (root == NULL) {
return new Node{ key, NULL, NULL, NULL, 1 }; // RED Node
}
25
else if (key < root->data) {
root->left = insertHelp(root->left, key);
root->left->parent = root;
// root->left = Node X
// root = X->parent
if (Root != root) {
if (root->color == root->left->color == 1)
f = true;
}
}
else {
root->right = insertHelp(root->right, key);
root->right->parent = root;
// root->right = Node X
// root = X->parent
if (Root != root) {
if (root->color == root->right->color == 1)
f = true;
}
}
// Xử lý 4 TH lệch
// *** Khi này (ll, lr, rr, rl = false) nên chưa xử lí liền
// *** Sau khi thoát 1 vòng đệ quy thì: root = X->parent-
>parent
// *** Tức là Node ông, lúc này ta quay Node ông
// Case 1 : Left left - Trái trái
if (ll) {
root = rotateRight(root);
root->color = 0;
root->right->color = 1;
ll = false;
}
// Case 2 : Right right - Phải phải
else if (rr) {
root = rotateLeft(root);
root->color = 0;
root->left->color = 1;
rr = false;
}
// Case 3 : Left right - Phải trái
else if (lr) {
root->left = rotateLeft(root->left);
root->left->parent = root;
root = rotateRight(root);
root->color = 0;
26
root->right->color = 1;
lr = false;
}
// Case 4 : Right left - Phải trái
else if (rl) {
root->right = rotateRight(root->right);
root->right->parent = root;
root = rotateLeft(root);
root->color = 0;
root->left->color = 1;
rl = false;
}
if (sib == NULL)
fixDoubleBlack(par);
else {
if (sib->color == 1) {
par->color = 1;
sib->color = 0;
if (sib->parent->left == sib)
par = rotateRight(par);
else
par = rotateLeft(par);
fixDoubleBlack(root);
}
else {
if (hasRedChild(sib)) {
if (sib->parent->left == sib) {
if (sib->left != NULL && sib->left-
>color) {
sib->left->color = sib->color;
sib->color = par->color;
28
par->color = 0;
par = rotateRight(par);
}
else {
sib->right->color = par->color;
par->color = 0;
sib = rotateLeft(sib);
par = rotateRight(par);
}
}
else {
if (sib->right != NULL && sib->right-
>color) {
sib->right->color = sib->color;
sib->color = par->color;
par->color = 0;
par = rotateLeft(par);
}
else {
sib->left->color = par->color;
par->color = 0;
sib = rotateRight(sib);
par = rotateLeft(par);
}
}
}
else {
sib->color = 1;
if (par->color == 0)
fixDoubleBlack(par);
else
par->color = 0;
}
}
}
}
void deleteNode(Node* vDelete) {
Node* uReplace;
if (vDelete->parent->left == vDelete)
par->left = NULL;
else
par->right = NULL;
}
delete vDelete;
return;
}
uReplace->parent = par;
if (uvBlack)
fixDoubleBlack(uReplace);
else
uReplace->color = 0;
}
return;
30
}
swapValues(uReplace, vDelete);
deleteNode(uReplace);
}
Node* search(int val) {
Node* temp = Root;
while (temp != NULL) {
if (val < temp->data) {
if (temp->left == NULL)
return NULL;
else
temp = temp->left;
}
else if (val == temp->data) {
break;
}
else {
if (temp->right == NULL)
return NULL;
else
temp = temp->right;
}
}
return temp;
}
void Delete(int val) {
Node* vDelete = search(val);
if (vDelete == NULL) {
cout << "\n ** Khong tim thay Node can xoa **\n";
return;
}
else {
deleteNode(vDelete);
}
return;
}
};
int main() {
RedBlackTree RBTree;
RBTree.Insert(1);
RBTree.Insert(4);
RBTree.Insert(6);
RBTree.Insert(3);
RBTree.Insert(5);
RBTree.Insert(7);
RBTree.Insert(8);
31
RBTree.Insert(2);
RBTree.Insert(9);
print2D(RBTree.Root);
cout << "\n---------------------------\n";
RBTree.Delete(5);
print2D(RBTree.Root);
return 0;
}
Kết quả:
32
PHẦN 4: ỨNG DỤNG
Một ứng dụng của cây đỏ đen – Red Black Tree:
Hầu hết các chức năng của thư viện cây BST tự cân bằng trong C++ (hoặc
TreeSet và TreeMap trong Java) đều sử dụng cấu trúc cây đỏ đen.
Lập lịch tiến trình CPU cho hệ điều hành Linux.
Áp dụng vào trong thuật toán phân cụm K-mean nhằm giảm độ phức tạp về thời
gian.
Ngoài ra MySQL cũng sử dụng cây đỏ đen cho các chỉ mục trên bảng
33
PHẦN 5: TỔNG KẾT
Ưu điểm:
Red Black Trees có độ phức tạp về thời gian được đảm bảo là O(log n) cho các
thao tác cơ bản như chèn, xóa và tìm kiếm.
Cây Đỏ Đen tự cân bằng.
Cây Đỏ Đen có thể được sử dụng trong nhiều ứng dụng do hiệu suất hiệu quả
và tính linh hoạt của chúng.
Cơ chế được sử dụng để duy trì sự cân bằng trong Cây Đỏ Đen tương đối đơn
giản và dễ hiểu.
Nhược điểm:
Red Black Trees yêu cầu thêm một bit lưu trữ cho mỗi nút để lưu trữ màu của
nút (đỏ hoặc đen).
Chi phí chèn và xóa cao hơn BST vì phải thực hiện chèn và đổi màu.
34
TÀI LIỆU THAM KHẢO
35