Дано дерево, необходимо найти сумму всех вершин
Дано дерево (вложенный объект), необходимо найти сумму всех вершин valueNode.
Примеры:
const tree = {
valueNode: 3,
next: [
{
valueNode: 1,
next: null,
},
{
valueNode: 3,
next: null,
},
{
valueNode: 2,
next: null,
},
{
valueNode: 2,
next: [
{
valueNode: 1,
next: null,
},
{
valueNode: 5,
next: null,
},
],
},
],
};
sumVertices(tree) // 17
const tree = {
valueNode: 1,
next: [
{
valueNode: 3,
next: null
},
{
valueNode: 2,
next: null
}
]
}
sumVertices(tree) // 6
Теория и объяснение
Что такое дерево в этой задаче
Каждый узел — это объект с числом valueNode и полем next, которое либо null, либо массив дочерних узлов.
Сумма по дереву — это сумма значения текущего узла плюс суммы всех его потомков.
Упрощённая схема (для первого примера):
(3)
/ / \ \
(1) (3) (2) (2)
/ \
(1) (5)
Сумма должна учитывать все числа в кружках.
Как мыслить рекурсивно
Рекурсивная идея строится на том, что “часть дерева” (поддерево) имеет тот же формат, что и всё дерево.
Определяется правило:
- Если узла нет (
nullилиundefined), сумма равна 0. - Если узел есть, сумма равна
node.valueNode + сумма всех детей.
В коде часто выглядит так:
- базовый случай:
if (root == null) return 0; - рекурсивный шаг: суммирование детей через цикл или
reduce().
Зачем здесь reduce()
reduce() превращает массив в одно значение, “накапливая” результат в аккумуляторе.
В задаче аккумулятором является сумма, которая увеличивается для каждого ребёнка.
Важный момент для начинающих: если массив детей пустой, reduce() должен иметь корректное начальное значение аккумулятора. В рекурсивном решении начальное значение задано как root.valueNode, поэтому даже при пустом списке детей возвращается значение текущего узла.
Почему итерация спасает от переполнения стека
Рекурсивный вызов функции создаёт новый кадр в стеке вызовов. Если глубина дерева слишком велика, стек заканчивается.
Итеративный обход хранит “что обработать дальше” в обычном массиве (стек/очередь), поэтому ограничение по глубине становится значительно мягче.
Корректная обработка next:null
Так как по условию next бывает null, требуется проверка перед обходом детей:
- надёжно:
Array.isArray(node.next) - допустимо и кратко:
const children = node.next ?? []
?. (например, node.next?.length) полезен, когда часть цепочки может быть null/undefined. Однако в данной задаче Array.isArray(...) даёт более строгую гарантию “это массив”, а не просто “что-то существует”.Сложность алгоритмов (время и память)
Пусть n — количество узлов в дереве.
- Время у всех решений: O(n), так как каждый узел посещается один раз.
- Память:
- Рекурсивный DFS: O(h), где
h— высота дерева (глубина), кадры лежат в стеке вызовов. - Итеративный DFS: O(h) в худшем случае, элементы лежат в собственном стеке.
- BFS: O(w), где
w— максимальная ширина уровня, элементы лежат в очереди.
- Рекурсивный DFS: O(h), где
Кратко: сумма valueNode находится простым обходом всех узлов (O(n)): рекурсивный DFS — самый наглядный, но на глубоком дереве может упасть из-за стека; итеративный DFS/BFS решает ту же задачу без рекурсии и обычно надёжнее для больших глубин.