HTML Diff
1 added 21 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Организация хранения данных в виде дерева позволяет обойти ограничения линейной структуры данных. Например, в последней нельзя организовать быстрыми поиск и вставку элементов одновременно. При этом в иерархической структуре данных можно эффективно выбирать и обновлять большие объемы данных.</p>
1 <p>Организация хранения данных в виде дерева позволяет обойти ограничения линейной структуры данных. Например, в последней нельзя организовать быстрыми поиск и вставку элементов одновременно. При этом в иерархической структуре данных можно эффективно выбирать и обновлять большие объемы данных.</p>
2 <p>Один из наиболее часто используемых и простых в реализации подвидов деревьев - бинарные деревья. Помимо организации поиска, бинарные деревья используют, когда разбирают математические выражения и компьютерные программы. Еще их используют, чтобы хранить данные для алгоритмов сжатия, а также они лежат в основе других структур данных, например, очереди с приоритетом, кучи и словари.</p>
2 <p>Один из наиболее часто используемых и простых в реализации подвидов деревьев - бинарные деревья. Помимо организации поиска, бинарные деревья используют, когда разбирают математические выражения и компьютерные программы. Еще их используют, чтобы хранить данные для алгоритмов сжатия, а также они лежат в основе других структур данных, например, очереди с приоритетом, кучи и словари.</p>
3 <p>В этом уроке мы познакомимся с устройством и особенностями бинарных деревьев и разберем основные операции с его узлами.</p>
3 <p>В этом уроке мы познакомимся с устройством и особенностями бинарных деревьев и разберем основные операции с его узлами.</p>
4 <h2>Что такое бинарные деревья</h2>
4 <h2>Что такое бинарные деревья</h2>
5 <p><strong>Бинарное дерево</strong>или<strong>двоичное дерево</strong>- это дерево, в котором у каждого из его узлов не более двух дочерних узлов. При этом каждый дочерний узел тоже представляет собой бинарное дерево.</p>
5 <p><strong>Бинарное дерево</strong>или<strong>двоичное дерево</strong>- это дерево, в котором у каждого из его узлов не более двух дочерних узлов. При этом каждый дочерний узел тоже представляет собой бинарное дерево.</p>
6 <p>Рассмотрим примеры деревьев на следующем рисунке:</p>
6 <p>Рассмотрим примеры деревьев на следующем рисунке:</p>
7 <p>Дерево (а) - бинарное. У каждого его узла не более двух дочерних узлов, у каждого из которых тоже не более двух дочерних. Например, узлы E, G, H, I и K - листовые, значит, у них ноль дочерних узлов. У узла C только один дочерний узел, а у узлов A, B, D и F по два дочерних.</p>
7 <p>Дерево (а) - бинарное. У каждого его узла не более двух дочерних узлов, у каждого из которых тоже не более двух дочерних. Например, узлы E, G, H, I и K - листовые, значит, у них ноль дочерних узлов. У узла C только один дочерний узел, а у узлов A, B, D и F по два дочерних.</p>
8 <p>Как только правило двух дочерних нарушается, то дерево перестает относиться к классу бинарных. Так, дерево (б) не является бинарным, так как у узла E три дочерних узла.</p>
8 <p>Как только правило двух дочерних нарушается, то дерево перестает относиться к классу бинарных. Так, дерево (б) не является бинарным, так как у узла E три дочерних узла.</p>
9 <p>Благодаря тому, что дочерних узлов всегда не больше двух, их называют<strong>правый</strong>и<strong>левый</strong>дочерние узлы.</p>
9 <p>Благодаря тому, что дочерних узлов всегда не больше двух, их называют<strong>правый</strong>и<strong>левый</strong>дочерние узлы.</p>
10 <p>Напомним, что есть завершенное и полное деревья. Для бинарных деревьев они приобретают следующий вид:</p>
10 <p>Напомним, что есть завершенное и полное деревья. Для бинарных деревьев они приобретают следующий вид:</p>
11 <ul><li><strong>Завершенное бинарное дерево</strong>- это бинарное дерево, в котором каждый уровень, кроме последнего, полностью заполнен, а заполнение последнего уровня производится слева направо</li>
11 <ul><li><strong>Завершенное бинарное дерево</strong>- это бинарное дерево, в котором каждый уровень, кроме последнего, полностью заполнен, а заполнение последнего уровня производится слева направо</li>
12 <li><strong>Полное бинарное дерево</strong>- это бинарное дерево, в котором у каждого узла ноль или два дочерних узла</li>
12 <li><strong>Полное бинарное дерево</strong>- это бинарное дерево, в котором у каждого узла ноль или два дочерних узла</li>
13 </ul><p>На практике чаще применяются два подвида бинарных деревьев: бинарные деревья поиска и бинарные кучи. Последние разберем в следующих уроках, а в этом сосредоточимся на первых.</p>
13 </ul><p>На практике чаще применяются два подвида бинарных деревьев: бинарные деревья поиска и бинарные кучи. Последние разберем в следующих уроках, а в этом сосредоточимся на первых.</p>
14 <h2>Что такое бинарные деревья поиска</h2>
14 <h2>Что такое бинарные деревья поиска</h2>
15 <p><strong>Бинарные деревья поиска</strong>отличаются от обычных бинарных деревьев тем, что хранят данные в отсортированном виде. Хранение значений внутри бинарного дерева поиска организовано в следующем виде:</p>
15 <p><strong>Бинарные деревья поиска</strong>отличаются от обычных бинарных деревьев тем, что хранят данные в отсортированном виде. Хранение значений внутри бинарного дерева поиска организовано в следующем виде:</p>
16 - <ul><li>се значения в узлах левого дочернего поддерева меньше значения родительского узла</li>
16 + <ul><li>Все значения в узлах левого дочернего поддерева меньше значения родительского узла</li>
17 <li>Все значения в узлах правого дочернего поддерева больше значения родительского узла</li>
17 <li>Все значения в узлах правого дочернего поддерева больше значения родительского узла</li>
18 <li>Каждый дочерний узел тоже является бинарным деревом поиска</li>
18 <li>Каждый дочерний узел тоже является бинарным деревом поиска</li>
19 </ul><p>Благодаря такой структуре хранения данных поиск узла в бинарном дереве поиска занимает O(logN). Это значительно меньше, если хранить значения в списках - O(N).</p>
19 </ul><p>Благодаря такой структуре хранения данных поиск узла в бинарном дереве поиска занимает O(logN). Это значительно меньше, если хранить значения в списках - O(N).</p>
20 <p>Если использовать отсортированный массив для хранения данных, скорость поиска элементов сравняется. Но при оценке времени вставки хранение в массиве значительно проигрывает работе с деревьями - O(N) против O(logN) соответственно.</p>
20 <p>Если использовать отсортированный массив для хранения данных, скорость поиска элементов сравняется. Но при оценке времени вставки хранение в массиве значительно проигрывает работе с деревьями - O(N) против O(logN) соответственно.</p>
21 <p>Такая высокая эффективность поиска в бинарном дереве поиска наблюдается только при сохранении его в сбалансированном состоянии - когда все уровни, кроме последнего полностью заполнены. Это значит, что любое добавление или удаление вершины может потребовать полное перестроение дерева. Более подробно об этой особенности мы поговорим в следующем уроке.</p>
21 <p>Такая высокая эффективность поиска в бинарном дереве поиска наблюдается только при сохранении его в сбалансированном состоянии - когда все уровни, кроме последнего полностью заполнены. Это значит, что любое добавление или удаление вершины может потребовать полное перестроение дерева. Более подробно об этой особенности мы поговорим в следующем уроке.</p>
22 <p>Рассмотрим на примере поиска элемента (10) сравнение операций поиска в отсортированном массиве, списке и бинарном дереве поиска:</p>
22 <p>Рассмотрим на примере поиска элемента (10) сравнение операций поиска в отсортированном массиве, списке и бинарном дереве поиска:</p>
23 <p>Для поиска в массиве применяется традиционный подход на основе половинного деления - метод дихотомии. На схеме можно видеть что аналогичная операция и на массиве, и на бинарном дереве поиска будет выполнена за три шага. В то же время поиск этого элемента на списке займет десять шагов.</p>
23 <p>Для поиска в массиве применяется традиционный подход на основе половинного деления - метод дихотомии. На схеме можно видеть что аналогичная операция и на массиве, и на бинарном дереве поиска будет выполнена за три шага. В то же время поиск этого элемента на списке займет десять шагов.</p>
24 <p>Далее поговорим о структуре бинарных деревьев и начнем реализовывать бинарное дерево в коде.</p>
24 <p>Далее поговорим о структуре бинарных деревьев и начнем реализовывать бинарное дерево в коде.</p>
25 <h2>Как бинарные деревья поиска реализуются в коде</h2>
25 <h2>Как бинарные деревья поиска реализуются в коде</h2>
26 <p>Напомним свойства бинарных деревьев:</p>
26 <p>Напомним свойства бинарных деревьев:</p>
27 <ul><li>Должно быть не более двух дочерних узлов</li>
27 <ul><li>Должно быть не более двух дочерних узлов</li>
28 <li>Дочерние узлы тоже должны быть бинарными деревьями</li>
28 <li>Дочерние узлы тоже должны быть бинарными деревьями</li>
29 <li>Дочерние узлы называют левыми и правыми</li>
29 <li>Дочерние узлы называют левыми и правыми</li>
30 </ul><p>В этом случае структура узла принимает следующий вид:</p>
30 </ul><p>В этом случае структура узла принимает следующий вид:</p>
31 - <p><strong>Javascript</strong></p>
 
32 - <p><strong>Java</strong></p>
 
33 - <p><strong>Python</strong></p>
 
34 - <p><strong>PHP</strong></p>
 
35 <p>Теперь нам необходимо расширить наш класс и реализовать необходимые операции, чтобы взаимодействовать с проектируемым классом. Начнем с операции поиска узла.</p>
31 <p>Теперь нам необходимо расширить наш класс и реализовать необходимые операции, чтобы взаимодействовать с проектируемым классом. Начнем с операции поиска узла.</p>
36 <p>С бинарными деревьями поиска можно выполнять следующие операции:</p>
32 <p>С бинарными деревьями поиска можно выполнять следующие операции:</p>
37 <ul><li>Искать узел</li>
33 <ul><li>Искать узел</li>
38 <li>Вставлять узел</li>
34 <li>Вставлять узел</li>
39 <li>Удалять узел</li>
35 <li>Удалять узел</li>
40 <li>Выполнять обход дерева</li>
36 <li>Выполнять обход дерева</li>
41 </ul><p>Разберем каждую операцию подробнее.</p>
37 </ul><p>Разберем каждую операцию подробнее.</p>
42 <h3>Поиск узла</h3>
38 <h3>Поиск узла</h3>
43 <p>Если искомое значение бинарного дерева поиска меньше значения узла, то оно может находиться только в левом поддереве. Искомое значение, которое больше значения узла, может быть только в правом поддереве. В таком случае мы можем применить рекурсивный подход и операция поиска будет выглядеть так:</p>
39 <p>Если искомое значение бинарного дерева поиска меньше значения узла, то оно может находиться только в левом поддереве. Искомое значение, которое больше значения узла, может быть только в правом поддереве. В таком случае мы можем применить рекурсивный подход и операция поиска будет выглядеть так:</p>
44 - <p><strong>Javascript</strong></p>
 
45 - <p><strong>Java</strong></p>
 
46 - <p><strong>Python</strong></p>
 
47 - <p><strong>PHP</strong></p>
 
48 <h3>Вставка узла</h3>
40 <h3>Вставка узла</h3>
49 <p>Все значения меньше текущего значения узла надо размещать в левом поддереве, а большие - в правом. Чтобы вставить новый узел, нужно проверить, что текущий узел не пуст. Далее может быть два пути:</p>
41 <p>Все значения меньше текущего значения узла надо размещать в левом поддереве, а большие - в правом. Чтобы вставить новый узел, нужно проверить, что текущий узел не пуст. Далее может быть два пути:</p>
50 <ul><li>Если это так, сравниваем значение со вставляемым. По результату сравнения проводим проверку для правого или левого поддеревьев</li>
42 <ul><li>Если это так, сравниваем значение со вставляемым. По результату сравнения проводим проверку для правого или левого поддеревьев</li>
51 <li>Если узел пуст, создаем новый и заполняем ссылку на текущий узел в качестве родителя</li>
43 <li>Если узел пуст, создаем новый и заполняем ссылку на текущий узел в качестве родителя</li>
52 </ul><p>Операция вставки использует рекурсивный подход аналогично операции поиска. Переведем данный алгоритм на язык JavaScript и получим следующий код метода вставки:</p>
44 </ul><p>Операция вставки использует рекурсивный подход аналогично операции поиска. Переведем данный алгоритм на язык JavaScript и получим следующий код метода вставки:</p>
53 - <p><strong>Javascript</strong></p>
 
54 - <p><strong>Java</strong></p>
 
55 - <p><strong>Python</strong></p>
 
56 - <p><strong>PHP</strong></p>
 
57 <h3>Удаление узла</h3>
45 <h3>Удаление узла</h3>
58 <p>Чтобы удалить элемент в связном списке, нужно найти его и ссылку на следующий элемент перенести в поле ссылки на предыдущем элементе.</p>
46 <p>Чтобы удалить элемент в связном списке, нужно найти его и ссылку на следующий элемент перенести в поле ссылки на предыдущем элементе.</p>
59 <p>Если необходимо удалить корневой узел или промежуточные вершины и сохранить структуру бинарного дерева поиска, выбирают один из следующих двух способов:</p>
47 <p>Если необходимо удалить корневой узел или промежуточные вершины и сохранить структуру бинарного дерева поиска, выбирают один из следующих двух способов:</p>
60 <ul><li>Находим и удаляем максимальный элемент левого поддерева и используем его значение в качестве корневого или промежуточного узла</li>
48 <ul><li>Находим и удаляем максимальный элемент левого поддерева и используем его значение в качестве корневого или промежуточного узла</li>
61 <li>Находим и удаляем минимальный элемент правого поддерева и используем его значение в качестве корневого или промежуточного узла</li>
49 <li>Находим и удаляем минимальный элемент правого поддерева и используем его значение в качестве корневого или промежуточного узла</li>
62 </ul><p>Оба варианта приемлемы для нашего дерева. Реализуем в коде второй вариант:</p>
50 </ul><p>Оба варианта приемлемы для нашего дерева. Реализуем в коде второй вариант:</p>
63 - <p><strong>Javascript</strong></p>
 
64 - <p><strong>Java</strong></p>
 
65 - <p><strong>Python</strong></p>
 
66 - <p><strong>PHP</strong></p>
 
67 <p>Реализация первого варианта будет выглядеть практически идентично. Только есть исключение: мы будем обходить правое поддерево и искать максимальное значение узла вместо минимального.</p>
51 <p>Реализация первого варианта будет выглядеть практически идентично. Только есть исключение: мы будем обходить правое поддерево и искать максимальное значение узла вместо минимального.</p>
68 <p>Для деревьев также существуют специфические операции, важнейшая из которых - обход дерева. Рассмотрим эту операцию подробнее.</p>
52 <p>Для деревьев также существуют специфические операции, важнейшая из которых - обход дерева. Рассмотрим эту операцию подробнее.</p>
69 <h3>Обход деревьев</h3>
53 <h3>Обход деревьев</h3>
70 <p>Когда мы поработали с деревом и нам нужно его сохранить в файл или вывести в печать, нам больше не нужен древовидный формат. Здесь мы прибегаем к<strong>обходу дерева</strong>- последовательное единоразовое посещение всех вершин дерева.</p>
54 <p>Когда мы поработали с деревом и нам нужно его сохранить в файл или вывести в печать, нам больше не нужен древовидный формат. Здесь мы прибегаем к<strong>обходу дерева</strong>- последовательное единоразовое посещение всех вершин дерева.</p>
71 <p>Существуют такие три варианта обхода деревьев:</p>
55 <p>Существуют такие три варианта обхода деревьев:</p>
72 <ul><li><strong>Прямой обход (КЛП)</strong>: корень → левое поддерево → правое поддерево</li>
56 <ul><li><strong>Прямой обход (КЛП)</strong>: корень → левое поддерево → правое поддерево</li>
73 <li><strong>Центрированный обход (ЛКП)</strong>: левое поддерево → корень → правое поддерево</li>
57 <li><strong>Центрированный обход (ЛКП)</strong>: левое поддерево → корень → правое поддерево</li>
74 <li><strong>Обратный обход (ЛПК)</strong>: левое поддерево → правое поддерево → корень</li>
58 <li><strong>Обратный обход (ЛПК)</strong>: левое поддерево → правое поддерево → корень</li>
75 </ul><p>Такие обходы называются<strong>поиском в глубину</strong>*. На каждом шаге итератор пытается продвинуться вертикально вниз по дереву перед тем, как перейти к родственному узлу - узлу на том же уровне. Еще есть<strong>поиск в ширину</strong>- обход узлов дерева по уровням: от корня и далее:</p>
59 </ul><p>Такие обходы называются<strong>поиском в глубину</strong>*. На каждом шаге итератор пытается продвинуться вертикально вниз по дереву перед тем, как перейти к родственному узлу - узлу на том же уровне. Еще есть<strong>поиск в ширину</strong>- обход узлов дерева по уровням: от корня и далее:</p>
76 <p>Реализация поиска в глубину может осуществляться или с использованием рекурсии, или с использованием стека. А поиск в ширину реализуется за счет использования очереди:</p>
60 <p>Реализация поиска в глубину может осуществляться или с использованием рекурсии, или с использованием стека. А поиск в ширину реализуется за счет использования очереди:</p>
77 - <p><strong>Javascript</strong></p>
 
78 - <p><strong>Java</strong></p>
 
79 - <p><strong>Python</strong></p>
 
80 - <p><strong>PHP</strong></p>
 
81 <h2>Выводы</h2>
61 <h2>Выводы</h2>
82 <p>В этом уроке мы познакомились с бинарными деревьями - структурой, которая лежит в основе многих других типов данных: множеств, куч, очередей с приоритетом. Еще мы подробно разобрали ключевой их подвид - бинарные деревья поиска и изучили методы работы с ними. Они позволят эффективно искать и обновлять большие объемы данных.</p>
62 <p>В этом уроке мы познакомились с бинарными деревьями - структурой, которая лежит в основе многих других типов данных: множеств, куч, очередей с приоритетом. Еще мы подробно разобрали ключевой их подвид - бинарные деревья поиска и изучили методы работы с ними. Они позволят эффективно искать и обновлять большие объемы данных.</p>