0Pricing
Coding Interview Prep · Урок

Вставка и поиск в BST

Реализуйте рекурсивные и итеративные вставку и поиск, отслеживайте путь по дереву для разных ключей и анализируйте худшую сложность для несбалансированных деревьев

«Вставка и поиск в BST» — бесплатный урок Coding Interview Prep на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Coding Interview Prep, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Coding Interview Prep содержит 4 уроков всего.

Определение свойства BST

Бинарное дерево поиска (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 работает подобно двоичному поиску: сравните целевое значение со значением текущего узла и перейдите рекурсивно в соответствующее поддерево. Если целевое значение равно текущему, верните узел. Если оно меньше, перейдите влево, а если больше — вправо. Если достигнут пустой узел, верните пустое значение. Временная сложность — 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 с тремя случаями: пустое значение (не найдено), совпадение (найдено) или изменение направления. Итеративный поиск также выполняется за 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, чтобы отслеживать последний непустой узел перед позицией вставки. Спускайтесь по дереву, как при поиске, запоминая родителя и последнее направление перехода. Достигнув пустого значения, присоедините новый узел к соответствующей стороне родителя. Всегда обрабатывайте отдельно особый случай пустого дерева, когда корень пуст.

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 минимальное значение всегда находится в крайнем левом узле: продолжайте переходить влево, пока не достигнете пустого значения. Максимальное значение находится в крайнем правом узле. Эти операции за 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

Быстрая проверка

Проверьте своё понимание концепций курса «Структуры данных & алгоритмы — подготовка к собеседованию по программированию», рассмотренных в этом уроке.

Итоги урока

В этом уроке Вы изучили: свойство BST — левое поддерево строго меньше, правое строго больше; поиск и вставку рекурсивным и итеративным способами за O(h); а также вырожденные деревья в худшем случае, где высота равна n. Далее мы рассмотрим удаление из BST и три его случая.

Часто задаваемые вопросы

Урок «Вставка и поиск в BST» бесплатный?

Да — полный текст урока «Вставка и поиск в BST» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Coding Interview Prep, подпишись на CoddyKit PRO. Курс Coding Interview Prep содержит 4 уроков всего.

Чему я научусь в уроке «Вставка и поиск в BST»?

Реализуйте рекурсивные и итеративные вставку и поиск, отслеживайте путь по дереву для разных ключей и анализируйте худшую сложность для несбалансированных деревьев Ты практикуешь Coding Interview Prep с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Coding Interview Prep?

Предыдущий опыт не требуется. Coding Interview Prep на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «Вставка и поиск в BST»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Coding Interview Prep?

Да. Каждый урок Coding Interview Prep включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Вставка и поиск в BST
  2. Удаление из BST: три случая
  3. Проверка BST и свойств симметричного обхода
  4. k-й наименьший элемент, сумма диапазона и преобразование BST в отсортированный массив
← Назад к Coding Interview Prep