تخطَّ إلى المحتوى

🧮 شرح هياكل البيانات والخوارزميات

الأشجار المتوازنة (Balanced Trees)

الدرس 25 من 32· ⏱ 3 دقائق قراءة

المشكلة: شجرة البحث الثنائية قد تنهار لقائمة

تعلّمت أن شجرة البحث الثنائية (BST) تعطي بحثًا بسرعة O(log n) — لكن هذا غير مضمون دائمًا. لو أدرجت عناصر مرتّبة سلفًا (1، 2، 3، 4، 5...)، الشجرة تصبح فعليًا قائمة مترابطة بشكل شجرة، والبحث يتدهور لـ O(n):

إدراج 1, 2, 3, 4, 5 بترتيب تصاعدي في BST عادية:

1
 \
  2
   \
    3
     \
      4
       \
        5
شجرة "منحرفة" بالكامل — لا فائدة من كونها شجرة أصلًا!

الحل: التوازن الذاتي (Self-Balancing)

الأشجار المتوازنة تعيد ترتيب نفسها تلقائيًا بعد كل إدراج/حذف للحفاظ على ارتفاع منخفض (تقريبًا log n دائمًا)، بغض النظر عن ترتيب الإدخال. أشهر نوعين: AVL وRed-Black Tree.

AVL Tree — التوازن الصارم

بعد كل إدراج، AVL تتحقّق: هل فرق الارتفاع بين الشجرة الفرعية اليسرى واليمنى لأي عقدة أكبر من 1؟ إذا نعم، تُجري دورانًا (Rotation) لإعادة التوازن فورًا.

حالة غير متوازنة (فرق ارتفاع = 2):        بعد دوران يمين:
      3                                       2
     /                                       / \
    2                    ←→                 1   3
   /
  1
class AVLNode:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None
        self.height = 1  # ارتفاع العقدة، يُحدَّث بعد كل عملية

def get_height(node):
    return node.height if node else 0

def get_balance(node):
    if not node:
        return 0
    return get_height(node.left) - get_height(node.right)

def rotate_right(y):
    x = y.left
    y.left = x.right
    x.right = y
    y.height = 1 + max(get_height(y.left), get_height(y.right))
    x.height = 1 + max(get_height(x.left), get_height(x.right))
    return x  # الجذر الجديد لهذا الجزء من الشجرة

أربع حالات اختلال ممكنة، كل منها له دوران مناسب: يسار-يسار (دوران يمين)، يمين-يمين (دوران يسار)، يسار-يمين، يمين-يسار (دوران مزدوج).

Red-Black Tree — توازن أخف تكلفة

Red-Black Tree تحقّق توازنًا "تقريبيًا" (أضعف صرامة من AVL) عبر تلوين كل عقدة أحمر أو أسود مع قواعد بسيطة (الجذر أسود دائمًا، عقدة حمراء لا تملك أبناء حمراء، كل مسار من الجذر للأوراق يحتوي نفس عدد العقد السوداء). التوازن الأخف يعني عمليات إعادة توازن أقل تكرارًا — لذلك تُستخدم بمكتبات قياسية كثيرة (مثل TreeMap بجافا وmap/set بـ C++).

AVL مقابل Red-Black — أيهما تختار؟

المعيارAVLRed-Black
صرامة التوازنأعلى (ارتفاع أقرب لـ log n دائمًا)أخف قليلًا
سرعة البحثأسرع قليلًا (شجرة أكثر توازنًا)جيدة جدًا
سرعة الإدراج/الحذفأبطأ (دورانات أكثر)أسرع (دورانات أقل)
الاستخدام الشائعقراءة كثيفة، كتابة قليلةكتابة وقراءة متوازنتين (الأشيع بمكتبات قياسية)

لماذا لا تبني هذا يدويًا عادةً؟

بالتطبيقات الحقيقية، نادرًا ما تكتب AVL/Red-Black من الصفر — تستخدم البنى الجاهزة بلغتك (TreeMap/TreeSet بجافا، std::map/std::set بـ C++، SortedList بمكتبات Python الخارجية). المهم فهم لماذا هي أسرع من BST العادية بأسوأ الحالات — هذا ما يُسأل عنه بالمقابلات التقنية غالبًا.

جدول سريع

البنيةأسوأ حالة بحثملاحظة
BST عاديةO(n)تنهار مع بيانات مرتّبة سلفًا
AVL / Red-BlackO(log n) مضمونة دائمًاتوازن تلقائي بعد كل عملية

أخطاء شائعة

  • الظن أن كل شجرة بحث ثنائية سرعتها O(log n) — هذا مضمون فقط بالأشجار المتوازنة، لا BST العادية.
  • محاولة حفظ الدورانات الأربعة عن ظهر قلب بدل فهم لماذا التوازن مهم أصلًا — الفهم أهم من التفاصيل التنفيذية بمعظم المقابلات.

🎯 التالي: لماذا الخوارزميات؟ — رحلة بصرية.

شرح الأشجار المتوازنة (Balanced Trees) — هياكل البيانات والخوارزميات بالعربي
الأشجار المتوازنة (Balanced Trees)هياكل البيانات والخوارزميات بالعربي · The Code Fix

📚 لمزيد من التعمّق في هياكل البيانات والخوارزميات، راجِع هياكل البيانات على ويكيبيديا.

هل كان هذا الدرس مفيدًا؟