# Представление двоичного дерева с помощью массива При представлении в виде списка единицей хранения двоичного дерева является узел `TreeNode`, а узлы соединяются между собой указателями. В предыдущем разделе были рассмотрены основные операции с двоичным деревом, представленным в виде списка. Можно ли представить двоичное дерево с помощью массива? Ответ положительный. ## Представление идеального двоичного дерева Сначала рассмотрим простой пример. Если дано идеальное двоичное дерево и все его узлы хранятся в массиве в порядке обхода по уровням, то каждому узлу соответствует уникальный индекс массива. На основе свойств обхода по уровням можно вывести формулу соответствия между индексами родительского и дочерних узлов: **если индекс узла равен $i$, то индекс его левого дочернего узла равен $2i + 1$, а правого -- $2i + 2$**. На рис. 7.12 показаны отношения соответствия между индексами узлов. ![Представление идеального двоичного дерева с помощью массива](array_representation_of_tree.assets/array_representation_binary_tree.png) **Формула соответствия играет роль, аналогичную ссылкам (указателям) в списке**. Имея любой узел в массиве, можно с помощью формулы получить доступ к его левому и правому дочерним узлам. ## Представление произвольного двоичного дерева Идеальное двоичное дерево является частным случаем. Обычно на средних уровнях двоичного дерева присутствует много пустых значений `None`. Но последовательность обхода по уровням не содержит этих `None`, поэтому невозможно по этой последовательности определить количество и расположение пустых значений. **Это означает, что существует множество структур двоичных деревьев, соответствующих данной последовательности обхода по уровням**. Для такого неидеального двоичного дерева вышеописанный метод представления с помощью массива уже не работает, см. рис. 7.13. ![Для одной последовательности обхода по уровням существует несколько возможных вариантов двоичного дерева](array_representation_of_tree.assets/array_representation_without_empty.png) Для решения этой проблемы **можно явно записать все значения `None` в последовательности обхода по уровням**. После такой обработки последовательность обхода по уровням уже может однозначно представлять двоичное дерево, как показано на рис. 7.14. Ниже приведен пример кода. === "Python" ```python title="" # Представление двоичного дерева с помощью массива # Использование None для обозначения пустых мест tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] ``` === "C++" ```cpp title="" /* Представление двоичного дерева с помощью массива */ // Использование INT_MAX для обозначения пустых мест vector tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; ``` === "Java" ```java title="" /* Представление двоичного дерева с помощью массива */ // Использование обертки Integer для типа int, чтобы можно было использовать null для обозначения пустых мест Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; ``` === "C#" ```csharp title="" /* Представление двоичного дерева с помощью массива */ // Использование nullable типа int?, чтобы можно было использовать null для обозначения пустых мест int?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Go" ```go title="" /* Представление двоичного дерева с помощью массива */ // Использование типа any для среза, чтобы можно было использовать nil для обозначения пустых мест tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} ``` === "Swift" ```swift title="" /* Представление двоичного дерева с помощью массива */ // Использование nullable типа Int?, чтобы можно было использовать nil для обозначения пустых мест let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] ``` === "JS" ```javascript title="" /* Представление двоичного дерева с помощью массива */ // Использование null для обозначения пустых мест let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "TS" ```typescript title="" /* Представление двоичного дерева с помощью массива */ // Использование null для обозначения пустых мест let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Dart" ```dart title="" /* Представление двоичного дерева с помощью массива */ // Использование nullable типа int?, чтобы можно было использовать null для обозначения пустых мест List tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` === "Rust" ```rust title="" /* Представление двоичного дерева с помощью массива */ // Использование None для обозначения пустых мест let tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)]; ``` === "C" ```c title="" /* Представление двоичного дерева с помощью массива */ // Использование максимального значения int для обозначения пустых мест, поэтому значения узлов не могут быть равны INT_MAX int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; ``` === "Kotlin" ```kotlin title="" /* Представление двоичного дерева с помощью массива */ // Использование null для обозначения пустых мест val tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ) ``` === "Ruby" ```ruby title="" ### Представление двоичного дерева с помощью массива ### # Использование nil для обозначения пустых мест tree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] ``` ![Представление произвольного двоичного дерева с помощью массива](array_representation_of_tree.assets/array_representation_with_empty.png) Стоит отметить, что **совершенное двоичное дерево очень удобно представлять с помощью массива**. Вспоминая определение совершенного двоичного дерева, `None` появляются только на самом нижнем уровне и в правой части, поэтому **все значения `None` обязательно находятся в конце последовательности обхода по уровням**. Это означает, что при использовании массива для представления совершенного двоичного дерева можно опустить хранение всех `None`, что очень удобно. На рис. 7.15 приведен пример такого представления. ![Представление совершенного двоичного дерева с помощью массива](array_representation_of_tree.assets/array_representation_complete_binary_tree.png) В коде ниже реализуется двоичное дерево, основанное на представлении с помощью массива, включая следующие операции. - Для заданного узла получение его значения, левого и правого дочернего узла, родительского узла. - Получение последовательностей обхода в прямом, симметричном, обратном порядке и в порядке обхода по уровням. ```src [file]{array_binary_tree}-[class]{array_binary_tree}-[func]{} ``` ## Преимущества и ограничения Представление двоичного дерева с помощью массива имеет следующие преимущества: - Массив хранится в непрерывной области памяти, что хорошо для кеширования. Скорость доступа и обхода достаточно высока. - Не требуется хранение указателей, что экономит пространство. - Позволяет выполнять произвольный доступ к узлам. Однако представление с помощью массива имеет и некоторые ограничения: - Хранение в массиве требует непрерывной области памяти, поэтому не подходит для хранения деревьев с очень большим объемом данных. - Добавление и удаление узлов требует выполнения операций вставки и удаления в массиве, которые менее эффективны. - Когда в двоичном дереве содержится много значений `None`, доля данных узлов в массиве низка, что приводит к низкой эффективности использования пространства.