Программа курса:
Разбор задачи: Заполнение указателей следующего права в каждом узле
Предложенный нами код решения:
def connect(root):
"""
Соединяет указатели `next` каждого узла в идеально сбалансированном бинарном дереве.
:param root: Корень дерева типа Node
:return: Корень дерева с установленными указателями `next`
"""
if not root:
return None
# Начинаем с верхнего уровня дерева
leftmost = root
while leftmost.left:
head = leftmost
while head:
# Соединяем левый ребенок с правым ребенком
head.left.next = head.right
# Соединяем правый ребенок с левым ребенком соседнего узла (если есть)
if head.next:
head.right.next = head.next.left
# Переходим к следующему узлу на текущем уровне
head = head.next
# Спускаемся на уровень ниже
leftmost = leftmost.left
return root
Разбор решения задачи
Функция connect используется для заполнения указателей next в идеально сбалансированном бинарном дереве. Рассмотрим решение шаг за шагом, включая текстовое описание и разбор ключевых фрагментов кода.
1. Проверка на пустое дерево
if not root:
return None
Этот фрагмент проверяет, является ли входное дерево пустым. Если корень дерева равен None, функция сразу возвращает None, так как заполнять указатели next нечему.
2. Инициализация переменной для работы с уровнями
leftmost = root
Здесь переменная leftmost используется для отслеживания самого левого узла текущего уровня. Мы начинаем с корня дерева, поскольку он находится на первом уровне.
3. Внешний цикл для перехода между уровнями
while leftmost.left:
Этот цикл работает, пока существует следующий уровень, на который можно спуститься. Поскольку дерево идеально сбалансировано, проверка leftmost.left гарантирует, что мы будем обрабатывать только уровни с узлами.
4. Внутренний цикл для работы с узлами на текущем уровне
head = leftmost
while head:
...
head = head.next
На каждом уровне мы начинаем с самого левого узла (head = leftmost) и последовательно переходим к следующему узлу на этом же уровне с помощью указателя next.
5. Соединение левого и правого ребенка
head.left.next = head.right
Здесь происходит ключевая операция: левый ребенок текущего узла соединяется с его правым ребенком через указатель next.
Пример:
Для узла со значением 2:
head.left— это левый ребенок, например,4.head.right— это правый ребенок, например,5.
В результатеhead.left.nextбудет указывать наhead.right.
6. Соединение правого ребенка с левым ребенком соседнего узла
if head.next:
head.right.next = head.next.left
Если у текущего узла (head) есть сосед справа (head.next), то правый ребенок текущего узла соединяется с левым ребенком соседнего узла.
Пример:
Для узла со значением 2 и его соседа 3:
head.right— это правый ребенок узла2, например,5.head.next.left— это левый ребенок узла3, например,6.
В результатеhead.right.nextбудет указывать наhead.next.left.
7. Переход к следующему узлу на уровне
head = head.next
Этот шаг перемещает указатель head к следующему узлу на текущем уровне.
8. Спуск на уровень ниже
leftmost = leftmost.left
После завершения обработки всех узлов текущего уровня, мы перемещаемся на уровень ниже, начиная с самого левого узла на этом уровне.
Итог
Функция обрабатывает дерево уровнями, начиная с корня и спускаясь вниз. На каждом уровне:
- Левый ребенок соединяется с правым.
- Правый ребенок соединяется с левым ребенком соседнего узла (если сосед есть).
Пример работы функции
Дерево до обработки:
1
/ \
2 3
/ \ / \
4 5 6 7
- Уровень 1: узел
1(нет соседей,next = None). - Уровень 2: узел
2соединяется с узлом3(2.next = 3). - Уровень 3: узлы
4 -> 5 -> 6 -> 7соединяются слева направо.
Дерево после обработки:
1 -> None
/ \
2 -> 3 -> None
/ \ / \
4->5->6->7 -> None