أشجار AVL: التدوير والإدراج والحذف C++ مثال
⚡ ملخص ذكي
أشجار AVL هي أشجار بحث ثنائية متوازنة ذاتيًا حيث يظل فرق الارتفاع بين الأشجار الفرعية اليسرى واليمنى لكل عقدة ضمن -1 أو 0 أو +1، مما يضمن أداء بحث O(log n).

ما هي أشجار AVL؟
الأشجار AVL هي أشجار بحث ثنائية يكون فيها فرق الارتفاع بين الشجرة الفرعية اليسرى واليمنى لكل عقدة هو -1 أو 0 أو +1. وهي أشجار بحث ثنائية متوازنة ذاتيًا تحافظ على وقت بحث لوغاريتمي، وقد سميت على اسم المخترعين أديلسون-فيلسكي ولانديس (AVL).
كيف تعمل شجرة AVL؟
لفهم سبب وجود أشجار AVL، انظر إلى ما يحدث خطأً في سهل شجرة البحث الثنائيةضع في اعتبارك هذه المفاتيح التي تم إدخالها بالترتيب المحدد:
تصور شجرة AVL
تنمو الشجرة خطيًا عند وصول المفاتيح بترتيب تصاعدي، مما يُقلل زمن البحث إلى O(n). وهذا يُفقد الغرض من شجرة البحث الثنائية (BST) - فالشجرة المتوازنة فقط هي التي تُبقي زمن البحث لوغاريتميًا. الآن، انظر إلى نفس المفاتيح مُدخلة بترتيب مختلف.
يؤدي استخدام نفس المفاتيح مع اختلاف ترتيب الإدخال إلى شكل أقل عمقًا، وبالتالي يتم تنفيذ كل عملية بحث في زمن O(log n). تحافظ أشجار AVL على هذا الشكل من خلال مراقبة الارتفاع عند كل عملية إدخال وتصحيح أي اختلال دون الإخلال بترتيب شجرة البحث الثنائية.
عامل التوازن في أشجار AVL
عامل التوازن (BF) tracks ارتفاع كل عقدة حتى تتمكن الشجرة من تحقيق التوازن الذاتي أثناء التشغيل.
خصائص عامل التوازن
شجرة عامل التوازن AVL
- عامل التوازن هو الفرق بين ارتفاع الشجرة الفرعية اليسرى وارتفاع الشجرة الفرعية اليمنى.
Balance factor(node) = height(node->left) − height(node->right)- القيم المسموح بها فقط هي -1 و 0 و +1.
- قيمة -1 تعني أن الشجرة الفرعية اليمنى تحتوي على مستوى إضافي واحد - العقدة ثقيلة من اليمين.
- قيمة +1 تعني أن الشجرة الفرعية اليسرى تحتوي على مستوى إضافي واحد - العقدة ثقيلة من اليسار.
- قيمة 0 تعني أن كلا الجانبين لهما نفس الارتفاع - العقدة متوازنة تمامًا.
دورات AVL
تُجرى عمليات التدوير كلما أدى إدخال عنصر أو حذفه إلى الإخلال بقاعدة عامل التوازن. الحالات الأربع هي: LL، RR، LR، وRL.
اليسار - دوران اليسار
يتم إجراء هذا التدوير عند إدراج عقدة جديدة عند الفرع الفرعي الأيسر للشجرة الفرعية اليسرى.
شجرة AVL لليسار – الدوران لليسار
يتم إجراء دوران واحد لليمين. يتم تفعيل هذه الحالة عندما يكون للعقدة BF +2 ويكون لابنها الأيسر BF +1.
يمين - دوران يمين
يتم تنفيذ هذا التدوير عند إدراج عقدة جديدة في الفرع الأيمن للشجرة الفرعية اليمنى.
يتم إجراء دوران واحد لليسار. يتم تفعيل هذه الحالة عندما يكون للعقدة BF −2 ويكون لابنها الأيمن BF −1.
اليمين - دوران اليسار
يتم تنفيذ هذا التدوير عند إدراج عقدة جديدة عند الفرع الأيسر للشجرة الفرعية اليمنى.
يتم تشغيلها عندما يكون BF(node) = −2 و BF(right-child) = +1. قم بتدوير الابن الأيمن إلى اليمين، ثم قم بتدوير العقدة إلى اليسار.
اليسار - دوران اليمين
يتم تنفيذ هذا التدوير عند إدراج عقدة جديدة عند الفرع الأيمن للشجرة الفرعية اليسرى.
يتم تشغيلها عندما يكون BF(node) = +2 و BF(left-child) = −1. قم بتدوير الابن الأيسر إلى اليسار، ثم قم بتدوير العقدة إلى اليمين.
الإدراج في أشجار AVL
عملية الإضافة تكاد تكون مطابقة لعملية إضافة بسيطة في شجرة البحث الثنائية. بعد كل عملية إضافة، تعود الشجرة إلى وضعها الأصلي وتعيد توازنها. تستغرق عملية الإضافة وقتًا قدره O(log n) في أسوأ الحالات.
تنفيذ إدراج شجرة AVL
الخطوة 1 : أدرج العقدة باستخدام خوارزمية BST القياسية. في المثال أعلاه، أدرج 160.
الخطوة 2 : قم بتحديث عامل التوازن لكل سلف على طول مسار الإدخال.
الخطوة 3 : إذا تجاوز أي سلف نطاق عامل التوازن، فقم بإجراء التدوير المطابق. في المثال، تم تجاوز عامل التوازن للعقدة 350، لذا فإن تدوير LL يعيد التوازن.
- If
BF(node) = +2وBF(left-child) = +1قم بإجراء دوران LL. - If
BF(node) = −2وBF(right-child) = −1قم بإجراء التناوب RR. - If
BF(node) = −2وBF(right-child) = +1قم بإجراء دوران RL. - If
BF(node) = +2وBF(left-child) = −1قم بإجراء دوران LR.
الحذف في أشجار AVL
تتبع عملية الحذف نفس منطق البحث الثنائي البسيط، ثم تعيد التوازن بعد ذلك.
الخطوة 1 : ابحث عن العنصر الموجود في الشجرة
الخطوة 2 : احذف العقدة باستخدام عملية حذف BST القياسية.
الخطوة 3 : هناك حالتان محتملتان.
حالة 1: الحذف من الشجرة الفرعية الصحيحة.
- 1A. If
BF(node) = +2وBF(left-child) = +1قم بإجراء دوران LL. - 1B. If
BF(node) = +2وBF(left-child) = −1قم بإجراء دوران LR. - 1C. If
BF(node) = +2وBF(left-child) = 0قم بإجراء دوران LL.
حالة 2: حذف من الشجرة الفرعية اليسرى.
- 2A. If
BF(node) = −2وBF(right-child) = −1قم بإجراء التناوب RR. - 2B. If
BF(node) = −2وBF(right-child) = +1قم بإجراء دوران RL. - 2C. If
BF(node) = −2وBF(right-child) = 0قم بإجراء التناوب RR.
C++ مثال على أشجار AVL
وفيما يلي C++ برنامج ينفذ أشجار 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); }
مثال عملي للكود أعلاه:
- انسخ الكود أعلاه واحفظه في ملف باسم
avl.cpp. - قم بتجميع الكود:
g++ avl.cpp -o run
- قم بتشغيل الكود.
./run
مزايا أشجار AVL
- يكون ارتفاع شجرة AVL متوازنًا دائمًا ولا يتجاوز أبدًا لوغاريتم N.
- البحث أسرع من شجرة البحث الثنائية العادية لأن الشجرة لا يمكن أن تتدهور.
- التوازن الذاتي تلقائي - لا حاجة لخطوة إعادة بناء.
- الأداء الحتمي يناسب الأنظمة التي تعمل في الوقت الحقيقي والفهارس الموجودة في الذاكرة.











