Бинарные деревья. Начало
На этом занятии мы затронем новую тему – бинарные деревья. Что это такое? Давайте предположим, что нам в программе нужно хранить и обрабатывать иерархические структуры данных, например, генеалогическое древо семьи, или структура каталогов и файлов и т.п.
То есть, везде, где есть некая начальная (корневая) вершина, от которой можно провести связи к дочерним, а от них – к следующим дочерним, получается иерархическая древовидная структура. Именно о таких структурах данных мы и будем вести речь на ближайших занятиях.
Начальная вершина дерева, от которого следуют все остальные, называется корнем дерева (root). Сами элементы дерева – узлами или вершинами (nodes). Причем, узел слева, называется левым, а справа – правым. Если у вершины нет левого или правого потомка, то обычно указывают значение NULL. Вершины, у которых нет потомков, называются листьями. Само же дерево, у которого каждая вершина может содержать не более двух дочерних узлов, называется бинарным или двоичным. Всю эту терминологию и обозначения, мы в дальнейшем будем активно использовать.
Структура бинарного дерева
Чаще всего в практике программирования используют бинарные деревья. Даже когда у вершины предполагается множество потомков, то зачастую задачу сводят все равно к бинарным деревьям или какой-либо другой структуре данных. Поэтому мы в рамках данного курса ограничимся именно бинарными деревьями.
Каждая вершина такого дерева содержит, как минимум, три поля:
- data – данные, хранимые в вершине дерева;
- left – ссылка (указатель) на потомка слева;
- right – ссылка (указатель) на потомка справа.
Иногда еще добавляют ссылку на родительский узел. Но чаще всего ограничиваются этими тремя полями. На вершину дерева, которая называется корнем, ведет указатель root. И через этот указатель происходит вся работа с бинарным деревом: добавление/удаление элементов; обход всех узлов дерева. То есть, из корневого узла можно пройти до любого другого в данном дереве. При этом, количество вершин, через которые нужно пройти, чтобы достичь некоторого узла, определяет уровень (level) дерева. Если дерево не содержит ни одной вершины, то root = NULL. Также, если у какой-либо вершины отсутствует левый или правый потомок, то соответствующий указатель принимает значение NULL.
Добавление вершин в бинарное дерево
Давайте теперь посмотрим, как можно формировать бинарное дерево, то есть, добавлять в него новые вершины. Обычно, новые узлы добавляются к свободному указателю (то есть, тот, который принимает значение NULL), начиная с корневого. То есть, первая вершина всегда добавляется в корень, и других вариантов просто нет. А вот далее, мы уже решаем добавить левый или правый узел. Строго говоря, ограничений здесь никаких нет. Алгоритм добавления может быть самым разным, исходя из поставленной задачи. Например, если нам нужно сформировать генеалогическое древо, то, очевидно, в вершинах последовательно должны указываться потомки: на нулевом уровне – некоторый человек; на первом – мама, папа; на втором – бабушки, дедушки и т.д. Но есть отдельный класс задач, когда в вершинах бинарного дерева хранятся данные, которые можно сравнивать на больше и меньше (например, числа), и добавление нового узла происходит по правилам:
- если добавляемое значение меньше значения в родительском узле, то новая вершина добавляет в левую ветвь, иначе – в правую;
- если добавляемое значение уже присутствует в дереве, то оно игнорируется (то есть, дубли отсутствуют).
Например, предположим, мы собираемся хранить в вершинах бинарного дерева целые числа. И будем последовательно добавлять узлы для значений: 10, 5, 7, 16, 13, 2, 20 Первое значение 10 просто добавляется в корневую вершину. Следующее значение 5. Смотрим от корня. Пять меньше 10. Тогда, согласно нашим правилам, это значение нужно записать в левую ветвь. Формируем новый узел со значением 5. Следующее значение 7. Снова идет от корня дерева. Семь меньше 10, переходим в левую ветвь. Здесь у нас узел со значением 5. И пять больше 7. Следовательно, семь нужно добавить в качестве правого узла дерева у вершины 5. Далее, идет число 16. Оно больше 10, поэтому добавляем его в качестве правой вершины у корневой. Затем, число 13. Оно также больше 10, но меньше 16. Следовательно, нужно добавить левый узел у вершины 16. Следующее значение 2 меньше 10 и меньше 5. Добавляем новый узел справа от узла 5. И последнее значение 20 – самое большое, поэтому оно станет самой крайней правой вершиной дерева.
Поиск значений в бинарном дереве
Хорошо, но что нам это дает? Смотрите, здесь появляется несколько интересных эффектов. Первый – это ускорение поиска заданного значения. Например, нам нужно определить, находится ли число 2 в последовательности чисел 10, 5, 7, 16, 13, 2, 20. Если бы мы хранили числа в обычном массиве:
Сбалансированные и несбалансированные деревья
Внимательный зритель сейчас мог бы мне возразить и сказать, что у нас все так хорошо получается, потому что я привел пример «красивого» бинарного дерева с минимальным числом уровней (всего два):
- АВЛ-дерево (AVL tree), разработан в 1968 году;
- красно-черное дерево (red-black tree), разработан в 1972 году;
- расширяющееся или косое дерево (splay tree), разработан в 1983 году.
Любой из этих методов может быть реализован в бинарном дереве и вызываться при добавлении новых вершин. В результате получаем более сбалансированное дерево при любых входных последовательностях данных. В данном курсе мы не будем углубляться в эти темы. Кому будет интересно, подробную информацию по этим методам легко найти на просторах Интернета. Отмечу только, что если нам известно, что в решаемой задаче данные (значения) поступают в случайном порядке, то в среднем, будет формироваться бинарное дерево близкое к сбалансированному. На этом мы завершим наше первое знакомство с бинарными деревьями, а на следующем продолжим и поговорим об алгоритмах обхода и удаления вершин бинарных деревьев. Курс по структурам данных: https://stepik.org/a/134212
Источник