Cây AVL: Xoay, chèn, xóa bằng C++ Ví dụ
⚡ Tóm tắt thông minh
Cây AVL là cây tìm kiếm nhị phân tự cân bằng, trong đó sự khác biệt về chiều cao giữa cây con bên trái và bên phải của mỗi nút nằm trong khoảng -1, 0 hoặc +1, đảm bảo hiệu suất tìm kiếm O(log n).
Cây AVL là gì?
Cây AVL Cây tìm kiếm nhị phân (BST) là loại cây mà sự khác biệt về chiều cao giữa cây con bên trái và bên phải của mỗi nút là -1, 0 hoặc +1. Chúng là các BST tự cân bằng duy trì thời gian tìm kiếm theo logarit, được đặt tên theo những người phát minh ra chúng là Adelson-Velsky và Landis (AVL).
Cây AVL hoạt động như thế nào?
Để hiểu lý do tại sao cây AVL tồn tại, hãy xem điều gì xảy ra sai với một cây thông thường. Cây tìm kiếm nhị phânHãy xem xét các khóa này được chèn theo thứ tự đã cho:
Trực quan hóa cây AVL
Cây phát triển tuyến tính khi các khóa được chèn vào theo thứ tự tăng dần, làm suy giảm độ phức tạp tìm kiếm xuống O(n). Điều đó làm mất đi mục đích của cây tìm kiếm nhị phân (BST) — chỉ có cây cân bằng mới giữ cho quá trình tìm kiếm có độ phức tạp logarit. Bây giờ hãy xem xét cùng các khóa đó được chèn theo thứ tự khác.
Cùng một khóa, nhưng thứ tự chèn khác nhau sẽ tạo ra hình dạng nông hơn, do đó mọi tìm kiếm đều chạy trong O(log n). Cây AVL đảm bảo hình dạng đó bằng cách theo dõi chiều cao trong mỗi lần chèn và điều chỉnh sự mất cân bằng mà không phá vỡ thứ tự của cây tìm kiếm nhị phân (BST).
Hệ số cân bằng trong cây AVL
Hệ số cân bằng (BF) tracks là chiều cao của mỗi nút để cây có thể tự cân bằng một cách linh hoạt.
Thuộc tính của yếu tố cân bằng
Cây AVL hệ số cân bằng
- Hệ số cân bằng là hiệu số giữa chiều cao của cây con bên trái và chiều cao của cây con bên phải.
Balance factor(node) = height(node->left) − height(node->right)- Các giá trị được cho phép duy nhất là -1, 0 và +1.
- Giá trị -1 có nghĩa là cây con bên phải chứa thêm một cấp độ — nút này nặng về phía bên phải.
- Giá trị +1 có nghĩa là cây con bên trái chứa thêm một cấp độ — nút này nặng về phía bên trái.
- Giá trị bằng 0 có nghĩa là cả hai phía có chiều cao bằng nhau — nút này hoàn toàn cân bằng.
Xoay AVL
Các phép quay được thực hiện bất cứ khi nào việc chèn hoặc xóa phá vỡ quy tắc hệ số cân bằng. Bốn trường hợp đó là LL, RR, LR và RL.
Xoay trái – Xoay trái
Việc xoay này được thực hiện khi một nút mới được chèn vào nút con bên trái của cây con bên trái.
Cây AVL Trái – Xoay Trái
Một phép quay phải duy nhất được thực hiện. Trường hợp này xảy ra khi một nút có BF +2 và nút con bên trái của nó có BF +1.
Phải – Xoay Phải
Việc xoay này được thực hiện khi một nút mới được chèn vào con bên phải của cây con bên phải.
Một phép quay trái duy nhất được thực hiện. Trường hợp này xảy ra khi một nút có BF −2 và nút con bên phải của nó có BF −1.
Xoay phải – trái
Việc xoay này được thực hiện khi một nút mới được chèn vào con trái của cây con bên phải.
Sự kiện này được kích hoạt khi BF(node) = −2 và BF(right-child) = +1. Xoay phải nút con, sau đó xoay trái nút chính.
Xoay Trái – Phải
Việc xoay này được thực hiện khi một nút mới được chèn vào nút con bên phải của cây con bên trái.
Sự kiện này được kích hoạt khi BF(node) = +2 và BF(left-child) = −1. Xoay trái nút con, sau đó xoay phải nút chính.
Chèn vào cây AVL
Thao tác chèn gần như giống hệt với thao tác chèn trong cây tìm kiếm nhị phân (BST). Sau mỗi lần chèn, cây sẽ di chuyển lên trên và cân bằng lại. Thao tác chèn mất thời gian O(log n) trong trường hợp xấu nhất.
Triển khai chèn cây AVL
Bước 1: Chèn nút bằng thuật toán cây tìm kiếm nhị phân (BST) tiêu chuẩn. Trong ví dụ trên, hãy chèn 160.
Bước 2: Cập nhật hệ số cân bằng của mỗi tổ tiên dọc theo đường dẫn chèn.
Bước 3: Nếu bất kỳ tổ tiên nào vi phạm phạm vi hệ số cân bằng, hãy thực hiện phép quay tương ứng. Trong ví dụ này, hệ số cân bằng của nút 350 bị vi phạm, vì vậy phép quay LL sẽ khôi phục lại sự cân bằng.
- If
BF(node) = +2vàBF(left-child) = +1Thực hiện xoay LL. - If
BF(node) = −2vàBF(right-child) = −1Thực hiện xoay RR. - If
BF(node) = −2vàBF(right-child) = +1Thực hiện phép quay RL. - If
BF(node) = +2vàBF(left-child) = −1Thực hiện phép quay LR.
Xóa trong cây AVL
Việc xóa tuân theo cùng một logic như cây tìm kiếm nhị phân thông thường và sẽ cân bằng lại sau đó.
Bước 1: Tìm phần tử trong cây.
Bước 2: Xóa nút bằng cách sử dụng phương pháp xóa cây tìm kiếm nhị phân chuẩn.
Bước 3: Có hai trường hợp có thể xảy ra.
Trường hợp 1: Xóa từ cây con bên phải.
- 1A. If
BF(node) = +2vàBF(left-child) = +1Thực hiện xoay LL. - Năm 1B. If
BF(node) = +2vàBF(left-child) = −1Thực hiện phép quay LR. - 1C. If
BF(node) = +2vàBF(left-child) = 0Thực hiện xoay LL.
Trường hợp 2: Xóa phần tử từ cây con bên trái.
- 2A. If
BF(node) = −2vàBF(right-child) = −1Thực hiện xoay RR. - Năm 2B. If
BF(node) = −2vàBF(right-child) = +1Thực hiện phép quay RL. - 2C. If
BF(node) = −2vàBF(right-child) = 0Thực hiện xoay RR.
C++ Ví dụ về cây AVL
Dưới đây là một C++ chương trình triển khai cây AVL:
#include <iostream> #include <queue> #include <unordered_map> using namespace std; struct node { struct node *left; int data; int height; struct node *right; }; class AVL { public: struct node *root; AVL() { this->root = NULL; } int calheight(struct node *p) { if (p->left && p->right) { if (p->left->height < p->right->height) return p->right->height + 1; else return p->left->height + 1; } else if (p->left && p->right == NULL) { return p->left->height + 1; } else if (p->left == NULL && p->right) { return p->right->height + 1; } return 0; } int bf(struct node *n) { if (n->left && n->right) return n->left->height - n->right->height; else if (n->left && n->right == NULL) return n->left->height; else if (n->left == NULL && n->right) return -n->right->height; return 0; } struct node *llrotation(struct node *n) { struct node *p = n; struct node *tp = p->left; p->left = tp->right; tp->right = p; return tp; } struct node *rrrotation(struct node *n) { struct node *p = n; struct node *tp = p->right; p->right = tp->left; tp->left = p; return tp; } struct node *rlrotation(struct node *n) { struct node *p = n; struct node *tp = p->right; struct node *tp2 = p->right->left; p->right = tp2->left; tp->left = tp2->right; tp2->left = p; tp2->right = tp; return tp2; } struct node *lrrotation(struct node *n) { struct node *p = n; struct node *tp = p->left; struct node *tp2 = p->left->right; p->left = tp2->right; tp->right = tp2->left; tp2->right = p; tp2->left = tp; return tp2; } struct node *insert(struct node *r, int data) { if (r == NULL) { r = new struct node; r->data = data; r->left = r->right = NULL; r->height = 1; return r; } if (data < r->data) r->left = insert(r->left, data); else r->right = insert(r->right, data); r->height = calheight(r); if (bf(r) == 2 && bf(r->left) == 1) r = llrotation(r); else if (bf(r) == -2 && bf(r->right) == -1) r = rrrotation(r); else if (bf(r) == -2 && bf(r->right) == 1) r = rlrotation(r); else if (bf(r) == 2 && bf(r->left) == -1) r = lrrotation(r); return r; } void levelorder_newline() { if (this->root == NULL) { cout << "\nEmpty tree\n"; return; } levelorder_newline(this->root); } void levelorder_newline(struct node *v) { queue<struct node *> q; struct node *cur; q.push(v); q.push(NULL); while (!q.empty()) { cur = q.front(); q.pop(); if (cur == NULL && q.size() != 0) { cout << "\n"; q.push(NULL); continue; } if (cur != NULL) { cout << " " << cur->data; if (cur->left != NULL) q.push(cur->left); if (cur->right != NULL) q.push(cur->right); } } } struct node *deleteNode(struct node *p, int data) { if (p->left == NULL && p->right == NULL) { if (p == this->root) this->root = NULL; delete p; return NULL; } struct node *q; if (p->data < data) p->right = deleteNode(p->right, data); else if (p->data > data) p->left = deleteNode(p->left, data); else { if (p->left != NULL) { q = inpre(p->left); p->data = q->data; p->left = deleteNode(p->left, q->data); } else { q = insuc(p->right); p->data = q->data; p->right = deleteNode(p->right, q->data); } } if (bf(p) == 2 && bf(p->left) == 1) p = llrotation(p); else if (bf(p) == 2 && bf(p->left) == -1) p = lrrotation(p); else if (bf(p) == 2 && bf(p->left) == 0) p = llrotation(p); else if (bf(p) == -2 && bf(p->right) == -1) p = rrrotation(p); else if (bf(p) == -2 && bf(p->right) == 1) p = rlrotation(p); else if (bf(p) == -2 && bf(p->right) == 0) p = rrrotation(p); return p; } struct node *inpre(struct node *p) { while (p->right != NULL) p = p->right; return p; } struct node *insuc(struct node *p) { while (p->left != NULL) p = p->left; return p; } ~AVL() {} }; int main() { AVL b; int c, x; do { cout << "\n1.Display levelorder on newline"; cout << "\n2.Insert"; cout << "\n3.Delete\n"; cout << "\n0.Exit\n"; cout << "\nChoice: "; cin >> c; switch (c) { case 1: b.levelorder_newline(); break; case 2: cout << "\nEnter no. "; cin >> x; b.root = b.insert(b.root, x); break; case 3: cout << "\nWhat to delete? "; cin >> x; b.root = b.deleteNode(b.root, x); break; case 0: break; } } while (c != 0); }
Ví dụ về cách chạy đoạn mã trên:
- Sao chép đoạn mã trên và lưu vào một tệp có tên là...
avl.cpp. - Biên dịch mã:
g++ avl.cpp -o run
- Chạy mã.
./run
Ưu điểm của cây AVL
- Chiều cao của cây AVL luôn cân bằng và không bao giờ vượt quá log N.
- Quá trình tìm kiếm nhanh hơn so với cây tìm kiếm nhị phân thông thường vì cây này không thể bị thoái hóa.
- Quá trình tự cân bằng diễn ra tự động — không cần bước thiết lập lại.
- Hiệu năng xác định phù hợp với các hệ thống thời gian thực và các chỉ mục trong bộ nhớ.












