Двоичное дерево: Построение и визуализация
Двоичное дерево: построение и визуализация в языке C
Дерево строится динамически. Это значит, что под каждый новый узел мы запрашиваем у операционной системы блок памяти с помощью функции malloc. После этого мы прописываем туда данные, а оба указателя обнуляем, так как новый узел изначально всегда является листом.
В зависимости от задачи, алгоритм вставки может различаться. Чаще всего в программировании используют Дерево поиска (BST), в котором соблюдается правило: все элементы меньше корня идут в левое поддерево, а все элементы больше или равные - в правое. Вот как выглядит рекурсивный код такой вставки:
Построение дерева: код целиком
#include <stdlib.h>
// Вспомогательная функция создания нового узла
struct node* create_node(int value) {
struct node* p = (struct node*)malloc(sizeof(struct node));
if (p != NULL) {
p->key = value;
p->left = NULL;
p->right = NULL;
}
return p;
}
// Рекурсивная вставка в двоичное дерево поиска
struct node* insert(struct node* root, int value) {
// Базовый случай: нашли пустое место, вставляем узел
if (root == NULL) {
return create_node(value);
}
// Рекурсивный спуск
if (value < root->key) {
// Идем налево и обновляем левый указатель
root->left = insert(root->left, value);
} else {
// Идем направо и обновляем правый указатель
root->right = insert(root->right, value);
}
// Возвращаем измененный корень поддерева обратно вверх
return root;
}
std::set и std::map). Программисту не нужно вручную выделять память. В Python дерево обычно моделируют объектами (class Node: ...). За счет сборщика мусора и отсутствия явных указателей код выглядит чище, но суть остается та же: узлы динамически порождаются и связываются друг с другом.
Визуализация в консоли
При отладке очень важно видеть форму дерева. Так как консоль плоская, мы можем рисовать дерево сбоку (повернутым на 90 градусов), используя рекурсивный обход и передавая текущий уровень глубины. Уровень задает отступ, а сами ветви рисуются псевдографикой: / ведет к правому сыну, \ - к левому, а | продолжает ветвь предка, через которую мы прошли насквозь.
Отступ узла складывается из отступа его отца плюс четыре знака: либо четыре пробела, либо палочка и три пробела. Эту накопленную строку удобно передавать в рекурсию готовым префиксом. Корень тут исключение: своей ветки у него нет, поэтому его ключ просто прижимается вправо к линии ствола форматом %2d. Однозначное число встанет на знак левее ствола, двузначное - вплотную к нему, и ни то, ни другое на ствол не наедет.
Визуализация дерева в консоли: код целиком
#include <stdio.h>
#include <string.h>
// Вывод дерева боком: правое поддерево сверху, левое снизу.
// prefix - столбик отступов и палочек, накопленный предками узла.
void print_tree(struct node* root, const char* prefix, int level, int is_right) {
if (root == NULL) return;
char child[128];
// Правое поддерево уходит вверх
snprintf(child, sizeof child, "%s%s", prefix,
level == 0 ? " " : (is_right ? " " : "| "));
print_tree(root->right, child, level + 1, 1);
// Ключ корня прижат вправо к линии ствола, у остальных узлов есть ветка
if (level == 0) {
printf("%2d\n", root->key);
} else {
printf("%s%s-- %d\n", prefix, is_right ? "/" : "\\", root->key);
}
// Левое поддерево уходит вниз
snprintf(child, sizeof child, "%s%s", prefix,
level == 0 ? " " : (is_right ? "| " : " "));
print_tree(root->left, child, level + 1, 0);
}
// Использование: print_tree(root, "", 0, 1);
//
// Для дерева 8 с детьми 3 и 10, у которых свои листья, вывод такой:
// /-- 12
// /-- 10
// | \-- 9
// 8
// | /-- 6
// \-- 3
// \-- 1
Коротко: главное
- Узел дерева моделируется структурой из информационного поля и двух ссылок на детей.
- Дерево растет динамически. Под каждый узел выделяется память, а новые узлы (листья) всегда инициализируются с
NULLссылками. - Связь узлов осуществляется через возвращение обновленного указателя из рекурсии (либо через двойную косвенность
**). - Визуализация дерева в терминале строится на базе обхода с передачей накопленного префикса: отступ задает уровень, а палочки
/,|и\рисуют ветви.
Проверьте себя
Чему равны указатели left и right у свежесозданного узла (до того как к нему добавят детей)?