التحقق من BST وخصائص الترتيب الوسطي
تحقق من كون الشجرة الثنائية BST باستخدام حدود min/max الممررة عبر الشجرة، وبالتأكد من أن الاجتياز الوسطي ينتج تسلسلًا مرتبًا
التحقق من 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 يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- إدراج BST والبحث فيه
- حذف BST: ثلاث حالات
- التحقق من BST وخصائص الترتيب الوسطي
- العنصر الأصغر رقم k ومجموع النطاق وتحويل BST إلى مصفوفة مرتبة