Вставка и поиск в BST
Реализуйте рекурсивные и итеративные вставку и поиск, отслеживайте путь по дереву для разных ключей и анализируйте худшую сложность для несбалансированных деревьев
«Вставка и поиск в BST» — бесплатный урок DSA Interview Prep на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения DSA Interview Prep, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс DSA 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) # 3BST в худшем случае: вырожденные деревья
Если вставлять отсортированную последовательность в 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 7BST из отсортированного массива
Построение сбалансированного по высоте 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) и разблокировать остальной курс 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 включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Вставка и поиск в BST
- Удаление из BST: три случая
- Проверка BST и свойств симметричного обхода
- k-й наименьший элемент, сумма диапазона и преобразование BST в отсортированный массив