🌳 الأشجار الثنائية في بايثون: هياكل البيانات التي تشبه الشجرة الحقيقية!

مرحباً بك في عالم هياكل البيانات الرائع! تخيل أن لديك مجموعة من البيانات، لكن بدلاً من وضعها في صف واحد (مثل القوائم)، يمكنك تنظيمها بطريقة تشبه الشجرة العائلية أو دليل الملفات على حاسوبك. هذا بالضبط ما تفعله الأشجار الثنائية (Binary Trees)! هيا بنا نتعرف عليها.


🤔 ما هي الشجرة الثنائية (Binary Tree)؟

الشجرة الثنائية هي هيكل بيانات غير خطي (Non-linear) يتكون من مجموعة من العقد (Nodes). كل عقدة تحتوي على:

  1. البيانات (Data) التي نخزنها.
  2. مؤشر (Pointer) للعقدة اليسرى (Left Child).
  3. مؤشر (Pointer) للعقدة اليمنى (Right Child).

تخيلها كشجرة عائلية مبسطة:

  • العقدة الجذر (Root Node) هي الجد الأكبر في أعلى الشجرة.
  • العقدة الابن (Child Node) هي العقدة المرتبطة مباشرةً بعقدة أخرى فوقها (تسمى الأب Parent).
  • العقدة الورقية (Leaf Node) هي العقدة التي ليس لها أبناء (أي أن مؤشريها اليسار واليمين يشيران إلى None).
# مثال: تمثيل عقدة واحدة في شجرة ثنائية
class TreeNode:
    def __init__(self, data):
        self.data = data  # البيانات المخزنة في العقدة
        self.left = None  # مؤشر للابن الأيسر (في البداية لا يوجد ابن)
        self.right = None # مؤشر للابن الأيمن (في البداية لا يوجد ابن)

# شرح التعليقات:
# __init__ هو المُنشئ (Constructor) الذي ينشئ العقدة.
# self.left و self.right سيشيران لاحقاً إلى عقد أخرى من نفس النوع (TreeNode).

🏗️ كيف نبني شجرة ثنائية بسيطة في بايثون؟

الآن لنبني شجرة صغيرة مكونة من 3 عقد. سنقوم بإنشاء العقد أولاً، ثم نربطها معاً يدوياً.

# الخطوة 1: إنشاء العقد الفردية
root = TreeNode("A")  # العقدة الجذر تحمل البيانات "A"
node_b = TreeNode("B")
node_c = TreeNode("C")

# الخطوة 2: ربط العقد معاً لتكوين الشجرة
# نريد شجرة حيث A هي الجذر، و B ابنها الأيسر، و C ابنها الأيمن.
root.left = node_b   # العقدة B أصبحت الابن الأيسر للجذر A
root.right = node_c  # العقدة C أصبحت الابن الأيمن للجذر A

# الآن أصبح لدينا هذه الشجرة:
#        A
#       / \
#      B   C
# حيث B و C هما عقدتان ورقيتان (ليس لهما أبناء).

🚶‍♂️ اجتياز الشجرة الثنائية (Tree Traversal)

كيف نزور كل عقدة في الشجرة لقراءة بياناتها أو معالجتها؟ تسمى هذه العملية الاجتياز (Traversal). هناك ثلاث طرق أساسية للاجتياز، تعتمد على ترتيب زيارة العقدة نفسها (N) مقارنة بأبنائها اليسار (L) واليمين (R).

1. الاجتياز الداخلي (Inorder Traversal) - LNR

القاعدة: اذهب لليسار أولاً، ثم قم بزيارة العقدة، ثم اذهب لليمين. النتيجة: يعطينا البيانات مرتبة تصاعدياً إذا كانت الشجرة شجرة بحث ثنائية (موضوع متقدم سنتعلمه لاحقاً).

def inorder_traversal(node):
    if node is not None:          # إذا كانت العقدة موجودة (ليست None)
        inorder_traversal(node.left)   # 1. اجتياز الفرع الأيسر كاملاً (L)
        print(node.data, end=" ")      # 2. زيارة العقدة الحالية (N)
        inorder_traversal(node.right)  # 3. اجتياز الفرع الأيمن كاملاً (R)

# لنطبقها على الشجرة السابقة (A -> B, C)
print("Inorder Traversal:")
inorder_traversal(root)  # المخرجات: B A C

2. الاجتياز المسبق (Preorder Traversal) - NLR

القاعدة: قم بزيارة العقدة أولاً، ثم اذهب لليسار، ثم اذهب لليمين. الاستخدام: مفيد لإنشاء نسخة من الشجرة.

def preorder_traversal(node):
    if node is not None:
        print(node.data, end=" ")      # 1. زيارة العقدة الحالية (N)
        preorder_traversal(node.left)  # 2. اجتياز الفرع الأيسر (L)
        preorder_traversal(node.right) # 3. اجتياز الفرع الأيمن (R)

print("\nPreorder Traversal:")
preorder_traversal(root)  # المخرجات: A B C

3. الاجتياز التالي (Postorder Traversal) - LRN

القاعدة: اذهب لليسار أولاً، ثم اذهب لليمين، ثم قم بزيارة العقدة. الاستخدام: مفيد لحذف الشجرة من الذاكرة.

def postorder_traversal(node):
    if node is not None:
        postorder_traversal(node.left)  # 1. اجتياز الفرع الأيسر (L)
        postorder_traversal(node.right) # 2. اجتياز الفرع الأيمن (R)
        print(node.data, end=" ")       # 3. زيارة العقدة الحالية (N)

print("\nPostorder Traversal:")
postorder_traversal(root)  # المخرجات: B C A

💡 لماذا نستخدم الأشجار الثنائية؟

تستخدم الأشجار الثنائية في العديد من التطبيقات العملية لأنها تجعل عمليات البحث والإدراج والحذف أسرع بكثير مقارنة بالقوائم الخطية في بعض الحالات. أمثلة على استخداماتها:

  • تمثيل التعبيرات الحسابية (مثلاً: (3 + 5) * 2).
  • إنشاء شجرة البحث الثنائية (BST) للبحث السريع عن البيانات.
  • خوارزميات الضغط (مثل شجرة هافمان).
  • التنقل في دليل الملفات.

🧠 خلاصة الدرس

تعلمنا اليوم:

  • أن الشجرة الثنائية هي هيكل بيانات غير خطي مكون من عقد، كل عقدة لها بيانات وابن أيسر وابن أيمن.
  • كيفية تمثيل عقدة الشجرة وإنشاء شجرة بسيطة في بايثون.
  • طرق اجتياز الشجرة الثلاث الأساسية: Inorder و Preorder و Postorder، وفارق بسيط في الترتيب يغير النتيجة تماماً!
  • بعض الاستخدامات العملية المهمة للأشجار الثنائية.

ماذا سنتعلم في الدرس القادم؟ 🌟

الآن وقد فهمت المبادئ الأساسية للأشجار الثنائية، حان الوقت لتعلم نوع خاص ومفيد جداً منها! في الدرس القادم، سنتعمق في شجرة البحث الثنائية (Binary Search Tree - BST). سنتعلم كيف تستغل هذه الشجرة الذكية ترتيب البيانات داخلها لجعل عمليات البحث والإضافة والحذف سريعة وفعّالة. استعد لترى كيف تتحول الفكرة النظرية إلى أداة قوية لحل المشكلات البرمجية الحقيقية!