Coding Interview Prep · درس

DFS بالترتيب الوسطي والقبلي والبعدي

نفّذ اجتيازات DFS الثلاثة بصورة ذاتية وتكرارية باستخدام مكدس صريح، مع شرح الحالات المناسبة لكل ترتيب

الدرس 2 من 413 خطوة

DFS بالترتيب الوسطي والقبلي والبعدي درس مجاني في Coding Interview Prep على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Coding Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.

ترتيبات اجتياز DFS الثلاثة

يزور DFS في الشجرة الثنائية العقد بواحد من ثلاثة ترتيبات، استنادًا إلى توقيت معالجة الجذر بالنسبة إلى أبنائه. الاجتياز التمهيدي: الجذر ← اليسار ← اليمين. الاجتياز الوسطي: اليسار ← الجذر ← اليمين. الاجتياز الختامي: اليسار ← اليمين ← الجذر. توضح الأسماء موضع الجذر في التسلسل. ويُعد فهم الترتيبات الثلاثة أمرًا أساسيًا، لأن المسائل المختلفة تتطلب ترتيبات مختلفة.

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

# Build: 1 -> left=2(left=4,right=5), right=3
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
# pre:  1 2 4 5 3
# in:   4 2 5 1 3
# post: 4 5 2 3 1
print('Tree built successfully')

الاجتياز التمهيدي التعاودي

في الاجتياز التمهيدي، تُعالج العقدة الحالية قبل شجرتيها الفرعيتين. ويحاكي هذا القراءة الطبيعية للشجرة من الأعلى إلى الأسفل، ويُستخدم لنسخ الأشجار، وتسلسلها، وتقييم تعبيرات البادئة. يتميز التنفيذ التعاودي بقصره الشديد، لكنه يبني مكدس استدعاءات بعمق O(h)، حيث تمثل h ارتفاع الشجرة.

def preorder(root):
    if not root:
        return []
    return [root.val] + preorder(root.left) + preorder(root.right)

# More memory-efficient with an accumulator:
def preorder_v2(root, result=None):
    if result is None:
        result = []
    if not root:
        return result
    result.append(root.val)  # PROCESS ROOT FIRST
    preorder_v2(root.left, result)
    preorder_v2(root.right, result)
    return result

root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
print(preorder_v2(root))  # [1, 2, 4, 5, 3]

الاجتياز الوسطي التعاودي

يزور الاجتياز الوسطي الشجرة الفرعية اليسرى، ثم الجذر، ثم الشجرة الفرعية اليمنى. وبالنسبة إلى شجرة بحث ثنائية، ينتج الاجتياز الوسطي دائمًا تسلسلًا مرتبًا؛ وتُستخدم هذه الخاصية في مسائل مثل التحقق من BST، والعنصر ذي الترتيب k الأصغر، وتحويل BST إلى مصفوفة مرتبة. وهو أهم اجتياز ينبغي معرفته لمسائل BST.

def inorder(root, result=None):
    if result is None:
        result = []
    if not root:
        return result
    inorder(root.left, result)   # left subtree first
    result.append(root.val)      # PROCESS ROOT MIDDLE
    inorder(root.right, result)  # right subtree last
    return result

# For a BST, inorder gives sorted output:
from collections import deque
def make_bst():
    root = TreeNode(4)
    root.left = TreeNode(2)
    root.right = TreeNode(6)
    root.left.left = TreeNode(1)
    root.left.right = TreeNode(3)
    return root

bst = make_bst()
print(inorder(bst))  # [1, 2, 3, 4, 6] - sorted!

الاجتياز الختامي التعاودي

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

def postorder(root, result=None):
    if result is None:
        result = []
    if not root:
        return result
    postorder(root.left, result)   # left subtree
    postorder(root.right, result)  # right subtree
    result.append(root.val)        # PROCESS ROOT LAST
    return result

# Use case: delete a tree (children before parent)
def delete_tree(root):
    if not root:
        return
    delete_tree(root.left)
    delete_tree(root.right)
    print(f'Deleting node {root.val}')  # safe: children gone

root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
print(postorder(root))  # [4, 2, 3, 1]

الاجتياز التمهيدي التكراري باستخدام مكدس

لتجنب حدود عمق الاستدعاء، نفّذوا DFS تكراريًا باستخدام مكدس صريح. في الاجتياز التمهيدي: ادفعوا الجذر إلى المكدس، ثم أزيلوا عقدة في كل تكرار، وسجّلوها، وادفعوا ابنها الأيمن ثم ابنها الأيسر، أي الأيمن أولًا حتى تُعالج العقدة اليسرى أولًا. يحاكي ذلك سلوك مكدس الاستدعاءات وفق مبدأ الوارد أخيرًا يخرج أولًا، وهو الأسلوب المعتاد للأشجار العميقة التي قد تفشل معها قيمة حد الاستدعاء التعاودي الافتراضية في Python، والبالغة 1000.

def preorder_iterative(root):
    if not root:
        return []
    result = []
    stack = [root]
    while stack:
        node = stack.pop()
        result.append(node.val)      # process now
        if node.right:               # push right FIRST
            stack.append(node.right)
        if node.left:                # push left second (popped first)
            stack.append(node.left)
    return result

root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
print(preorder_iterative(root))  # [1, 2, 4, 5, 3]

الاجتياز الوسطي التكراري باستخدام مكدس

يُعد الاجتياز الوسطي التكراري أكثر تعقيدًا قليلًا. استخدموا مكدسًا ومؤشرًا هو curr: اتجهوا إلى اليسار قدر الإمكان، وادفعوا كل عقدة إلى المكدس. وعندما لا يعود بالإمكان التقدم إلى اليسار، أزيلوا العقدة، وسجّلوها، ثم انتقلوا إلى اليمين. يُعد هذا النمط — الدفع إلى اليسار حتى الوصول إلى قيمة فارغة، ثم الإزالة والمعالجة، ثم الانتقال إلى اليمين — تقنية تكرارية أساسية تظهر في مسائل مكرّر BST.

def inorder_iterative(root):
    result = []
    stack = []
    curr = root
    while curr or stack:
        # Go as far left as possible
        while curr:
            stack.append(curr)
            curr = curr.left
        # Pop and process
        curr = stack.pop()
        result.append(curr.val)
        # Move to right subtree
        curr = curr.right
    return result

root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
print(inorder_iterative(root))  # [4, 2, 5, 1, 3]

الاجتياز الختامي التكراري باستخدام مكدسين

يتضمن الاجتياز الختامي التكراري حيلة ذكية: نفّذوا اجتيازًا تمهيديًا معدّلًا (الجذر ← اليمين ← اليسار)، ثم اجمعوا النتائج بترتيب عكسي. ادفعوا الجذر، ثم أزيلوا عقدة وأضيفوها في مقدمة النتيجة، وادفعوا الابن الأيسر ثم الأيمن. يحوّل العكس ترتيب الجذر ← اليمين ← اليسار إلى اليسار ← اليمين ← الجذر، وهو ترتيب الاجتياز الختامي تمامًا. ويمكن بديلًا استخدام مؤشر prev لتتبع العقدة التي تمت زيارتها أخيرًا باستخدام مكدس واحد.

from collections import deque

def postorder_iterative(root):
    if not root:
        return []
    result = deque()
    stack = [root]
    while stack:
        node = stack.pop()
        result.appendleft(node.val)  # prepend = reverse pre-order
        if node.left:
            stack.append(node.left)  # push left first
        if node.right:
            stack.append(node.right) # push right second
    return list(result)

root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
print(postorder_iterative(root))  # [4, 5, 2, 3, 1]

متى تختارون كل اجتياز

يُعد اختيار الاجتياز المناسب مؤشرًا مهمًا في مقابلات التوظيف. استخدموا الاجتياز التمهيدي عندما تحتاجون إلى معالجة الأب قبل أبنائه، مثل تسلسل الشجرة أو نسخ بنيتها. واستخدموا الاجتياز الوسطي مع أشجار BST للاستفادة من الترتيب المرتب. واستخدموا الاجتياز الختامي عند حساب قيم تعتمد على كلا الابنين، مثل الارتفاع والقطر ومجموع الشجرة الفرعية. ويُفضّل BFS في مسائل أقصر مسار وتجميع العقد حسب المستوى.

# Pattern summary:
# Pre-order  -> top-down: parent info flows DOWN to children
# In-order   -> BST sorted property, kth element, validate BST
# Post-order -> bottom-up: children info flows UP to parent
# BFS        -> shortest path, level grouping, level averages

# Example: compute subtree sum (post-order because
# we need left + right sum before computing total)
def subtree_sum(root):
    if not root:
        return 0
    left = subtree_sum(root.left)
    right = subtree_sum(root.right)
    return root.val + left + right  # uses children FIRST

root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
print(subtree_sum(root))  # 6

اجتياز Morris الوسطي: مساحة O(1)

يحقق اجتياز Morris اجتيازًا وسطيًا بمساحة O(1) من خلال تعديل الشجرة مؤقتًا. لكل عقدة تحتوي على شجرة فرعية يسرى، ابحثوا عن السلف الوسطي، أي العقدة الموجودة في أقصى اليمين من الشجرة الفرعية اليسرى، ثم اربطوا مؤشرها الأيمن بالعقدة الحالية. بعد زيارتها، أعيدوا المؤشر إلى حالته الأصلية. تُطرح هذه التقنية المتقدمة في مقابلات المستوى الأعلى عندما يسأل المحاور: «هل يمكنكم تنفيذ ذلك بمساحة إضافية قدرها O(1)؟»

def morris_inorder(root):
    result = []
    curr = root
    while curr:
        if not curr.left:
            result.append(curr.val)
            curr = curr.right
        else:
            # Find in-order predecessor
            pred = curr.left
            while pred.right and pred.right != curr:
                pred = pred.right
            if not pred.right:
                # Make thread and move left
                pred.right = curr
                curr = curr.left
            else:
                # Remove thread, visit, move right
                pred.right = None
                result.append(curr.val)
                curr = curr.right
    return result

root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(6)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
print(morris_inorder(root))  # [1, 2, 3, 4, 6]

إعادة بناء الشجرة من عمليات الاجتياز

عند إعطائكم مصفوفتَي الاجتياز التمهيدي والوسطي، يمكنكم إعادة بناء الشجرة الأصلية. يكون العنصر الأول في الاجتياز التمهيدي هو الجذر دائمًا. ابحثوا عن هذا الجذر في مصفوفة الاجتياز الوسطي؛ فكل ما يقع إلى يساره ينتمي إلى الشجرة الفرعية اليسرى، وكل ما يقع إلى يمينه ينتمي إلى الشجرة الفرعية اليمنى. طبّقوا ذلك تعاوُديًا على المصفوفتين الفرعيتين. تبلغ التعقيدية الزمنية O(n) عند استخدام بحث فهرس في جدول تجزئة.

def build_from_preorder_inorder(preorder, inorder):
    if not preorder:
        return None
    root_val = preorder[0]
    root = TreeNode(root_val)
    mid = inorder.index(root_val)
    # left subtree: inorder[0:mid], preorder[1:mid+1]
    root.left = build_from_preorder_inorder(
        preorder[1:mid+1], inorder[:mid])
    # right subtree: inorder[mid+1:], preorder[mid+1:]
    root.right = build_from_preorder_inorder(
        preorder[mid+1:], inorder[mid+1:])
    return root

pre = [3, 9, 20, 15, 7]
ino = [9, 3, 15, 20, 7]
root = build_from_preorder_inorder(pre, ino)
print(root.val, root.left.val, root.right.val)  # 3 9 20

ملخص الزمن والمساحة لعمليات الاجتياز

تتمتع عمليات اجتياز DFS الثلاثة جميعًا بتعقيد زمني قدره O(n)، لأن كل عقدة تُزار مرة واحدة بالضبط. أما التعقيد المكاني فهو O(h)، حيث تمثل h ارتفاع الشجرة؛ أي O(log n) في الأشجار المتوازنة وO(n) في الأشجار المنحرفة، بسبب مكدس الاستدعاءات أو المكدس الصريح. تتجنب التطبيقات التكرارية حد الاستدعاء التعاودي في Python، لكنها تستخدم التعقيد التقاربي نفسه من حيث المساحة. ويحقق اجتياز Morris وحده مساحة O(1) من خلال إعادة استخدام المؤشرات اليمنى للشجرة.

# Complexity table:
# Traversal  | Time | Space (recursion) | Space (iterative)
# -----------|------|-------------------|------------------
# Pre-order  | O(n) | O(h)              | O(h)
# In-order   | O(n) | O(h)              | O(h)
# Post-order | O(n) | O(h)              | O(h)
# Morris     | O(n) | O(1)              | O(1)
# BFS        | O(n) | O(w)              | O(w)
# h = height, w = max width
# Balanced: h = log n, w = n/2
# Skewed: h = n, w = 1
print('O(n) time for all traversals')

تحقق سريع

اختبروا مدى فهمكم لمفاهيم Data Structures & Algorithms — Coding Interview Prep الواردة في هذا الدرس.

مراجعة الدرس

تعلمتم في هذا الدرس: ترتيبات اجتياز DFS الثلاثة، وهي التمهيدي والوسطي والختامي، ومتى تختارون كلًّا منها، والتطبيقات التعاودية والتكرارية باستخدام مكدس صريح، وتقنية Morris ذات المساحة O(1). بعد ذلك سنستكشف حساب قطر الشجرات الثنائية وارتفاعها وتوازنها.

البدء مجانًا

تعلم Coding Interview Prep مع معلم ذكاء اصطناعي — مجانًا

اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.

الدورات
90
الدروس
360

الأسئلة الشائعة

هل درس «DFS بالترتيب الوسطي والقبلي والبعدي» مجاني؟

نعم — نص درس «DFS بالترتيب الوسطي والقبلي والبعدي» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Coding Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.

ماذا ستتعلم في «DFS بالترتيب الوسطي والقبلي والبعدي»؟

نفّذ اجتيازات DFS الثلاثة بصورة ذاتية وتكرارية باستخدام مكدس صريح، مع شرح الحالات المناسبة لكل ترتيب تتمرن على Coding Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Coding Interview Prep؟

لا تُشترط خبرة سابقة. Coding Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «DFS بالترتيب الوسطي والقبلي والبعدي»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Coding Interview Prep هذا؟

نعم. كل درس في Coding Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. فئة TreeNode واجتياز BFS حسب المستويات
  2. DFS بالترتيب الوسطي والقبلي والبعدي
  3. قطر الأشجار وارتفاعها وتوازنها
  4. مجموع المسار والسلف المشترك الأدنى
← العودة إلى Coding Interview Prep