Деревья
Настало время познакомиться с самыми часто встречающимися нелинейными структурами данных — деревьями.
Деревья
Дерево — конечное множество узлов , в котором есть один выделенный узел — корень, а остальные узлы разделены на непересекающихся множеств , которые тоже являются деревьями.
Эти деревья называются поддеревьями корня , а их количество — степенью корня. Обозначается степень корня .
Несмотря на однонаправленность определения, все у нас хорошо. Степень существует для любых узлов, и определяется как количество поддеревьев этого узла.
Здесь много несостыковок со школьным определением дерева «дерево — связный граф без циклов». В школьном определении нет выделенного корня, да и степень вершины на больше... Пугаться не стоит, просто это другие деревья. Школьные деревья в нашем мире тоже есть, просто называются свободными деревьями.
Из нашего определения выстраивается чёткая иерархия деревьев, их поддеревьев, поддеревьев их поддеревьев и т. д. Неимоверно большой соблазн взять и нарисовать линии, соединяющие корень с поддеревьями (или с корнями поддеревьев, что одно и то же). Тем самым иерархия показана еще и графически.
Теперь мы явно задали наше дерево как граф, врешины которого — все узлы , а ребро между двумя узлами рисуется, если один из этих узлов является корнем поддерева второго узла.
Типы узлов деревьев
Если степень узла равна , то этот узел называется листом (leaf) или терминальным узлом (terminal node).
Если степень узла больше , то этот узел называется внутренним узлом или узлом ветвления (branch node).
Величины
Пусть — узел дерева .
Глубиной вершины называется количество рёбер на пути от корня дерева до вершины .
Глубина корня всегда , то есть .
В некоторой литературе часто упоминается понятие уровень вершины — величина, на большая глубины вершины. Мы такое понятие использовать не будем.
Деревья в памяти компьютера
Есть два принципиально разных способа хранения деревьев в компьютере: безперспективный и малоперспективный.
Связан такой скудный рацион с тем, что дерево — структура нелинейная. Как бы мы не пытались линеаризовать связи, у нас ничего не выйдет. Или мы потеряем некоторые операции (точнее, сделаем их не такими быстрыми, как хотелось бы), или получим жуткий оверхед по времени или по памяти.
Можно пройтись по нашей выстроенной иерархии снизу вверх, храня связь между ребёнком и родителем. Тогда в каждом узле просто появится указатель на родителя. Если указатель у какой-то вершины на родителя пустой, значит родителя нет и эта вершина — глобальный корень.
Теперь мы можем подниматься вверх по иерархии, просто проходя по ссылке на родителя. Осталось научится спускаться вниз. Для этого нам нужно хранить список из детей. Я пока не накладываю никаких ограничений на этот список: это может быть динамический массив с доступом на время , это может быть двусвязный циклический список, обеспечивающий быстрое добавление и удаление детей, или какая-то из комбинация типа блочного списка.
struct node:
node parent
list[node] children
some additional dataУ нас в любом случае будет оверхед на этот самый список.