DSA Interview Prep · درس

التحقق من BST وخصائص الترتيب الوسطي

تحقق من كون الشجرة الثنائية BST باستخدام حدود min/max الممررة عبر الشجرة، وبالتأكد من أن الاجتياز الوسطي ينتج تسلسلًا مرتبًا

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

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

مشكلة التحقق من صحة BST

تُعد مسألة Validate BST (LeetCode #98) من مسائل المقابلات الكلاسيكية التي تربك كثيرًا من المتقدمين. يتحقق النهج الساذج فقط من أن قيمة كل عقدة أكبر من ابنها الأيسر وأصغر من ابنها الأيمن، لكن هذا التحقق المحلي غير كافٍ. فقد تحقق عقدة في شجرة فرعية القاعدة المحلية، مع أنها تنتهك خاصية BST العامة. الحل الصحيح هو تمرير حدود دنيا وعليا عبر الشجرة.

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

# Why local check fails:
#     5
#    / \
#   1   4
#      / \
#     3   6
# Node 4's children (3, 6) satisfy local rule,
# but 4 < 5 and is in the RIGHT subtree -- BST violated!
print('Local check is insufficient -- use min/max bounds')

نهج الحدود الدنيا والعليا

مرّر الحد الأدنى والحد الأعلى عبر الاستدعاءات التكرارية. عند كل عقدة، تحقّق من أن low < node.val < high. عند الاستدعاء التكراري على اليسار، حدّث الحد الأعلى إلى node.val (يجب أن تكون قيم الشجرة الفرعية اليسرى أصغر). وعند الاستدعاء التكراري على اليمين، حدّث الحد الأدنى إلى node.val (يجب أن تكون قيم الشجرة الفرعية اليمنى أكبر). ابدأ بـ low = -infinity وhigh = +infinity.

def is_valid_bst(root, low=float('-inf'), high=float('inf')):
    if not root:
        return True
    if not (low < root.val < high):
        return False
    return (is_valid_bst(root.left, low, root.val) and
            is_valid_bst(root.right, root.val, high))

# Valid BST:
valid = TreeNode(5)
valid.left = TreeNode(3)
valid.right = TreeNode(7)
print(is_valid_bst(valid))  # True

# Invalid BST (3 is in wrong subtree conceptually):
invalid = TreeNode(5)
invalid.left = TreeNode(1)
invalid.right = TreeNode(4)
invalid.right.left = TreeNode(3)
invalid.right.right = TreeNode(6)
print(is_valid_bst(invalid))  # False (4 < 5 in right subtree)

التحقق باستخدام الاجتياز الوسطي

يستخدم نهج بديل للتحقق خاصية الترتيب في الاجتياز الوسطي في BST: اجمع تسلسل الاجتياز الوسطي وتحقق من كونه متزايدًا بشكل صارم. يتميز هذا النهج بالأناقة وسهولة التفكير فيه. لكنه يستخدم مساحة إضافية مقدارها O(n) لتخزين التسلسل. وتستخدم نسخة محسّنة مؤشر prev واحدًا أثناء الاجتياز للتحقق من كل زوج من دون تخزين التسلسل كاملًا.

def is_valid_bst_inorder(root):
    prev = [float('-inf')]

    def inorder(node):
        if not node:
            return True
        if not inorder(node.left):
            return False
        if node.val <= prev[0]:  # not strictly increasing
            return False
        prev[0] = node.val
        return inorder(node.right)

    return inorder(root)

valid = TreeNode(5)
valid.left = TreeNode(3)
valid.right = TreeNode(7)
valid.left.left = TreeNode(1)
valid.left.right = TreeNode(4)
print(is_valid_bst_inorder(valid))   # True

invalid = TreeNode(5)
invalid.left = TreeNode(6)  # 6 > 5 in left subtree!
print(is_valid_bst_inorder(invalid)) # False

مقارنة نهجي التحقق

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

# Both approaches:
# Time: O(n) -- visit each node once
# Space: O(h) -- call stack depth
# h = O(log n) balanced, O(n) skewed

# When to choose which:
# min/max bounds:
#   - Cleaner for trees with constraints beyond BST
#   - No global state (purely functional)
# in-order prev:
#   - More intuitive (sorted sequence check)
#   - Easier to convert to iterative with a stack

print('Both O(n) time, O(h) space -- choose by clarity')

استعادة BST: عقدتان متبادلتان

تُصلح مسألة Recover BST (LeetCode #99) شجرة BST استُبدلت فيها عقدتان بالضبط. أثناء الاجتياز الوسطي، تنتج BST المرتبة ترتيبًا تصاعديًا. إذا استُبدلت عقدتان، فستظهر مخالفة واحدة أو مخالفتان يكون فيهما prev.val > current.val. تكون العقدة الأولى في المخالفة الأولى والعقدة الثانية في المخالفة الأخيرة هما العقدتين في الموضع الخطأ — فبدّل قيمتيهما.

def recover_tree(root):
    first = second = prev = None

    def inorder(node):
        nonlocal first, second, prev
        if not node:
            return
        inorder(node.left)
        if prev and prev.val > node.val:
            if not first:
                first = prev    # first violator
            second = node       # always update second
        prev = node
        inorder(node.right)

    inorder(root)
    # Swap values of the two misplaced nodes
    if first and second:
        first.val, second.val = second.val, first.val

root = TreeNode(3)
root.left = TreeNode(1)
root.right = TreeNode(4)
root.right.left = TreeNode(2)  # 2 and 3 are swapped
recover_tree(root)
print(root.val, root.right.left.val)  # 2, 3 (fixed)

تحويل BST إلى مصفوفة مرتبة

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

def bst_to_sorted_array(root):
    result = []
    def inorder(node):
        if not node:
            return
        inorder(node.left)
        result.append(node.val)
        inorder(node.right)
    inorder(root)
    return result

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

دمج شجرتي BST

من أجل دمج شجرتي BST في مصفوفة مرتبة واحدة، حوّل كل شجرة إلى مصفوفة مرتبة في O(n) وO(m)، ثم ادمج المصفوفتين المرتبتين باستخدام خطوة الدمج من خوارزمية merge sort في O(n+m). الزمن الكلي: O(n+m). إذا كنت تحتاج إلى الناتج على هيئة BST متوازنة، فمرّر المصفوفة المرتبة المدمجة إلى خوارزمية تحويل المصفوفة المرتبة إلى BST. هذا التفكيك إلى مسائل فرعية بسيطة هو سمة الحل الواضح والمناسب للمقابلات.

def merge_two_bsts(root1, root2):
    def inorder(node, arr):
        if not node:
            return
        inorder(node.left, arr)
        arr.append(node.val)
        inorder(node.right, arr)

    arr1, arr2 = [], []
    inorder(root1, arr1)
    inorder(root2, arr2)

    # Merge two sorted arrays
    merged = []
    i = j = 0
    while i < len(arr1) and j < len(arr2):
        if arr1[i] <= arr2[j]:
            merged.append(arr1[i]); i += 1
        else:
            merged.append(arr2[j]); j += 1
    merged.extend(arr1[i:])
    merged.extend(arr2[j:])
    return merged

r1 = TreeNode(2); r1.left = TreeNode(1); r1.right = TreeNode(4)
r2 = TreeNode(3); r2.left = TreeNode(0); r2.right = TreeNode(5)
print(merge_two_bsts(r1, r2))  # [0, 1, 2, 3, 4, 5]

عدّ العقد ضمن نطاق في BST

احسب عدد العقد التي تقع قيمها ضمن النطاق [low, high]. يستغرق الفحص الشامل بالاجتياز الوسطي O(n). أما الإصدار الذي يستفيد من خصائص BST فيستبعد الفروع: إذا كانت قيمة العقدة الحالية أصغر من low، فلا فائدة من فحص الشجرة الفرعية اليسرى (فجميع القيم فيها أصغر من low أيضًا). وبالمثل، استبعد الشجرة الفرعية اليمنى عندما تكون القيمة الحالية أكبر من high. الحالة المتوسطة هي O(log n + k)، حيث k هو عدد العقد المطابقة.

def range_sum_bst(root, low, high):
    if not root:
        return 0
    total = 0
    if low <= root.val <= high:
        total += root.val
    if root.val > low:   # left subtree may have values >= low
        total += range_sum_bst(root.left, low, high)
    if root.val < high:  # right subtree may have values <= high
        total += range_sum_bst(root.right, low, high)
    return total

root = TreeNode(10)
root.left = TreeNode(5)
root.right = TreeNode(15)
root.left.left = TreeNode(3)
root.left.right = TreeNode(7)
root.right.right = TreeNode(18)
print(range_sum_bst(root, 7, 15))  # 7 + 10 + 15 = 32

القيم المكررة وBST الصارمة وغير الصارمة

يستخدم الثابت القياسي في BST متباينة صارمة: تكون قيم الشجرة الفرعية اليسرى أصغر strictly، وقيم الشجرة الفرعية اليمنى أكبر strictly. تسمح بعض المسائل بالقيم المكررة، فتضعها في الشجرة الفرعية اليسرى (left <= root) أو اليمنى (root < right). عند التحقق من صحة BST، تحقّق دائمًا من التعريف الوارد في نص المسألة. يتعامل نهج الحدود الدنيا والعليا مع كلا النوعين عبر تعديل ما إذا كان فحص الحدود صارمًا أم شاملًا.

# Strict BST (LeetCode default): left < root < right
def is_valid_strict(root, lo=float('-inf'), hi=float('inf')):
    if not root:
        return True
    if not (lo < root.val < hi):  # STRICT inequalities
        return False
    return (is_valid_strict(root.left, lo, root.val) and
            is_valid_strict(root.right, root.val, hi))

# Non-strict BST (allows duplicates in right): left <= root < right
def is_valid_nonstrict(root, lo=float('-inf'), hi=float('inf')):
    if not root:
        return True
    if not (lo <= root.val < hi):  # NOTE: <= for left side
        return False
    return (is_valid_nonstrict(root.left, lo, root.val + 1) and
            is_valid_nonstrict(root.right, root.val, hi))

print('Always clarify strict vs non-strict with interviewer')

الاجتياز الوسطي: أداة BST الشاملة

يُعد الاجتياز الوسطي الأداة متعددة الاستخدامات في مسائل BST. كلما سألت مسألة عن الترتيب التصاعدي، أو العنصر رقم k، أو الاستعلامات عن النطاقات، أو خصائص التسلسل، ففكّر فيما إذا كان الاجتياز الوسطي (أو الاجتياز العكسي) يمنحك الإجابة. تختزل معظم المسائل الخاصة بـ BST إلى: الاجتياز بترتيب تصاعدي وتنفيذ عملية عند كل خطوة. إن سرعة التعرّف على هذا النمط مهارة أساسية في المقابلات.

# Problems solved elegantly with in-order:
# 1. Validate BST: check prev <= curr during in-order
# 2. Kth smallest: count k steps in in-order
# 3. Kth largest: count k steps in REVERSE in-order
# 4. Closest value to target: find crossover in in-order
# 5. BST to sorted array: collect in-order into list
# 6. Recover BST: find 1-2 violations in in-order
# 7. Sum of range [lo, hi]: accumulate during in-order

# The key insight: in-order visits BST nodes in sorted order.
# All sorted-order reasoning translates to in-order DFS.
print('In-order = sorted access = foundation of BST reasoning')

أقرب قيمة في BST

اعثر على العقدة التي تكون قيمتها الأقرب إلى قيمة مستهدفة معينة. استفد من ترتيب BST: ابدأ من الجذر، وتتبّع أقرب قيمة عثرت عليها حتى الآن، وانتقل في اتجاه القيمة المستهدفة (اذهب يسارًا إذا كانت القيمة المستهدفة أصغر، ويمينًا إذا كانت أكبر). هذا النهج، الذي يعمل في O(h)، أكثر كفاءة من الفحص بالاجتياز الوسطي، ويبرهن على الاستخدام الفعال لخاصية BST لتقليص مساحة البحث.

def closest_value(root, target):
    closest = root.val
    curr = root
    while curr:
        if abs(curr.val - target) < abs(closest - target):
            closest = curr.val
        if target < curr.val:
            curr = curr.left
        elif target > curr.val:
            curr = curr.right
        else:
            break  # exact match
    return closest

root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(5)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
print(closest_value(root, 3.714286))  # 4

تحقق سريع

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

مراجعة الدرس

في هذا الدرس تعلّمت: التحقق من صحة BST باستخدام الحدود الدنيا والعليا (لتجنب مشكلة التحقق المحلي)، والبديل القائم على مؤشر prev في الاجتياز الوسطي للتحقق، والاجتياز الوسطي بوصفه الأداة الشاملة لـ BST في مجاميع النطاقات، وأقرب قيمة، وعمليات الدمج. في الجزء التالي سنستخدم خصائص الاجتياز الوسطي في BST للعثور على العنصر رقم k الأصغر.

البدء مجانًا

تعلم Python مع معلم ذكاء اصطناعي — مجانًا

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

الدورات
30
الدروس
120

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

هل درس «التحقق من BST وخصائص الترتيب الوسطي» مجاني؟

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

ماذا ستتعلم في «التحقق من BST وخصائص الترتيب الوسطي»؟

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

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

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

كم من الوقت يستغرق درس «التحقق من BST وخصائص الترتيب الوسطي»؟

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

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

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

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

  1. إدراج BST والبحث فيه
  2. حذف BST: ثلاث حالات
  3. التحقق من BST وخصائص الترتيب الوسطي
  4. العنصر الأصغر رقم k ومجموع النطاق وتحويل BST إلى مصفوفة مرتبة
← العودة إلى DSA Interview Prep