0Pricing
DSA Interview Prep · درس

إدراج BST والبحث فيه

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

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

تعريف خاصية BST

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

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

# Valid BST:
#       4
#      / \
#     2   6
#    / \ / \
#   1  3 5  7
# For node 4: left subtree {1,2,3} < 4 < right subtree {5,6,7}
# This holds recursively for EVERY node in the tree.
print('BST property: left < node < right at every level')

البحث العودي في BST

يعمل البحث في BST مثل البحث الثنائي: قارنوا القيمة المستهدفة بقيمة العقدة الحالية، ثم انتقلوا عوديًا إلى الشجرة الفرعية المناسبة. إذا ساوت القيمة المستهدفة قيمة العقدة الحالية، فأعيدوا العقدة. وإذا كانت أصغر، فاتجهوا إلى اليسار؛ وإذا كانت أكبر، فاتجهوا إلى اليمين. أعيدوا null عند الوصول إلى عقدة فارغة. التعقيد الزمني هو O(h) — أي O(log n) في الأشجار المتوازنة وO(n) في الأشجار المنحرفة.

def search_bst(root, val):
    if not root:
        return None  # not found
    if root.val == val:
        return root  # found
    if val < root.val:
        return search_bst(root.left, val)
    else:
        return search_bst(root.right, val)

root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(7)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)

result = search_bst(root, 2)
print(result.val if result else 'Not found')  # 2
result = search_bst(root, 5)
print(result.val if result else 'Not found')  # Not found

البحث التكراري في BST

يتجنب البحث التكراري الحمل الزائد لمكدس الاستدعاءات، ويُفضَّل في الشيفرة المستخدمة في بيئة الإنتاج. استخدموا مؤشرًا curr يتحرك نزولًا في الشجرة، متبعًا الاتجاه الأيسر أو الأيمن بناءً على المقارنات. هذه حلقة while بسيطة بثلاث حالات: null (لم يتم العثور على القيمة)، أو تطابق (تم العثور عليها)، أو تعديل الاتجاه. يعمل البحث التكراري أيضًا بزمن O(h)، لكنه يستخدم مساحة O(1) مقابل O(h) للإصدار العودي.

def search_bst_iterative(root, val):
    curr = root
    while curr:
        if val == curr.val:
            return curr
        elif val < curr.val:
            curr = curr.left
        else:
            curr = curr.right
    return None  # not found

root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(7)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)

node = search_bst_iterative(root, 3)
print(node.val if node else 'Not found')  # 3
print(search_bst_iterative(root, 9))     # None

الإدراج العودي في BST

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

def insert_bst(root, val):
    if not root:
        return TreeNode(val)  # create new node here
    if val < root.val:
        root.left = insert_bst(root.left, val)
    elif val > root.val:
        root.right = insert_bst(root.right, val)
    # val == root.val: duplicate, do nothing (or handle as needed)
    return root

root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(7)
root = insert_bst(root, 1)
root = insert_bst(root, 5)
# Tree is now: 4, left=2(left=1), right=7(left=5)
print(root.right.left.val)  # 5

الإدراج التكراري في BST

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

def insert_bst_iterative(root, val):
    new_node = TreeNode(val)
    if not root:
        return new_node
    curr = root
    while True:
        if val < curr.val:
            if curr.left is None:
                curr.left = new_node
                break
            curr = curr.left
        else:  # val > curr.val
            if curr.right is None:
                curr.right = new_node
                break
            curr = curr.right
    return root

root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(7)
root = insert_bst_iterative(root, 3)
print(root.left.right.val)  # 3

أسوأ حالات BST: الأشجار المنحرفة

إذا أدرجتم تسلسلًا مرتبًا في BST، فستحصلون على شجرة منحرفة تتحول إلى قائمة مرتبطة. تصبح عمليات البحث والإدراج والحذف جميعها بزمن O(n). ولهذا السبب توجد أشجار BST المتوازنة، مثل أشجار AVL والأشجار الحمراء والسوداء. في المقابلات، اذكروا دائمًا أسوأ حالة عند السؤال عن تعقيد BST — فالقول «O(log n) في المتوسط، وO(n) في أسوأ حالة للأشجار غير المتوازنة» يُظهر عمق فهمكم.

# Inserting 1, 2, 3, 4, 5 into a BST:
# 1
#  \
#   2
#    \
#     3
#      \
#       4
#        \
#         5
# This is a right-skewed tree: search is O(n) not O(log n)

root = None
for val in [1, 2, 3, 4, 5]:
    root = insert_bst(root, val)

# Verify the skew
node = root
depth = 0
while node:
    depth += 1
    node = node.right
print(f'Height: {depth}')  # 5 = O(n), not O(log n)

العثور على الحد الأدنى والحد الأقصى

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

def find_min(root):
    while root.left:
        root = root.left
    return root

def find_max(root):
    while root.right:
        root = root.right
    return root

root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(7)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
root.right.right = TreeNode(9)

print(find_min(root).val)  # 1
print(find_max(root).val)  # 9

اللاحق والسابق بالترتيب الوسطي

العقدة اللاحقة بالترتيب الوسطي لعقدة ما هي العقدة ذات أصغر قيمة أكبر من قيمة تلك العقدة. إذا كانت للعقدة شجرة فرعية يمنى، فاللاحقة هي find_min(node.right). وإذا لم تكن لها شجرة فرعية يمنى، فاللاحقة هي أدنى سلف تكون فيه العقدة المعطاة ضمن الشجرة الفرعية اليسرى. يُعد فهم ذلك أمرًا أساسيًا لمسائل حذف BST ومسائل مكرّر BST.

def inorder_successor(root, p):
    successor = None
    while root:
        if p.val < root.val:
            successor = root  # possible successor
            root = root.left
        else:
            root = root.right
    return successor

def inorder_predecessor(root, p):
    predecessor = None
    while root:
        if p.val > root.val:
            predecessor = root  # possible predecessor
            root = root.right
        else:
            root = root.left
    return predecessor

root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(6)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
p = root.left  # node with val=2
print(inorder_successor(root, p).val)   # 3
print(inorder_predecessor(root, p).val) # 1

تحليل تعقيد البحث في BST

يعتمد أداء BST بالكامل على ارتفاع الشجرة. في BST متوازنة تحتوي على n عقدة، يكون الارتفاع O(log n)، مما يجعل البحث والإدراج والحذف بزمن O(log n). أما في BST منحرفة، فيكون الارتفاع O(n)، مما يجعل جميع العمليات بزمن O(n). لا تحتوي Python على BST متوازنة مضمّنة (بخلاف TreeMap في Java)، لذا يمكنكم تنفيذ AVL أو الأشجار الحمراء والسوداء بأنفسكم، أو استخدام sortedcontainers.SortedList، أو الاعتماد على كومة في حالات استخدام قوائم الأولوية.

# Python's BST alternatives:
# 1. heapq - min/max heap, O(log n) push/pop
# 2. sortedcontainers.SortedList (third-party, often allowed)
# 3. Manual AVL or Red-Black (rarely required in interviews)

# When interviews say 'use a BST':
# - LeetCode: implement TreeNode-based solution
# - Real interview: mention sortedcontainers or Java TreeMap equivalent
# - O(log n) operations matter when you need ordered access

# For pure insert/lookup without ordering: use dict (O(1) average)
print('Use heap for priority, dict for lookup, BST for ordered range')

حالات خاصة عند الإدراج في BST

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

def insert_bst_no_duplicates(root, val):
    if not root:
        return TreeNode(val)
    if val < root.val:
        root.left = insert_bst_no_duplicates(root.left, val)
    elif val > root.val:
        root.right = insert_bst_no_duplicates(root.right, val)
    # else: val == root.val -> duplicate, skip
    return root

# Test all edge cases:
root = None
root = insert_bst_no_duplicates(root, 5)  # empty tree
root = insert_bst_no_duplicates(root, 5)  # duplicate
root = insert_bst_no_duplicates(root, 3)
root = insert_bst_no_duplicates(root, 7)
print(root.val, root.left.val, root.right.val)  # 5 3 7

إنشاء BST من مصفوفة مرتبة

يستخدم إنشاء BST متوازنة من حيث الارتفاع من مصفوفة مرتبة (LeetCode #108) أسلوب فرق تسد: يصبح العنصر الأوسط هو الجذر، ويصبح النصف الأيسر الشجرة الفرعية اليسرى، ويصبح النصف الأيمن الشجرة الفرعية اليمنى. يضمن ذلك شجرة متوازنة بارتفاع O(log n). ويكون التعقيد الزمني O(n)، لأن كل عنصر يُعالَج مرة واحدة.

def sorted_array_to_bst(nums):
    if not nums:
        return None
    mid = len(nums) // 2
    root = TreeNode(nums[mid])
    root.left = sorted_array_to_bst(nums[:mid])
    root.right = sorted_array_to_bst(nums[mid+1:])
    return root

nums = [-10, -3, 0, 5, 9]
root = sorted_array_to_bst(nums)
print(root.val)        # 0 (middle element)
print(root.left.val)   # -3
print(root.right.val)  # 9

تحقق سريع

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

مراجعة الدرس

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

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

هل درس «إدراج BST والبحث فيه» مجاني؟

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

ماذا ستتعلم في «إدراج BST والبحث فيه»؟

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

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

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

كم من الوقت يستغرق درس «إدراج BST والبحث فيه»؟

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

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

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

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

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