Двоичные деревья в C: полное руководство для начинающих

Последнее обновление: Январь 14 2026
Автор: TecnoDigital
  • Иерархическая структура с узлами, имеющими максимум двух дочерних элементов; включает корень, листья и уровни.
  • Преимущества: эффективный поиск и вставка, иерархическое представление и динамическая гибкость по сравнению с массивами.
  • Основные операции: обход таблиц (входящие, исходные, последующие), поиск, вставка и удаление для сортировки и управления данными.
Бинарные деревья в C

Добро пожаловать в это всеобъемлющее руководство по двоичным деревьям на языке C. В этой статье мы рассмотрим основы двоичных деревьев и способы их реализации на языке программирования C. Если вы новичок в программировании или просто хотите улучшить свои навыки C, это руководство для вас.

Бинарные деревья — это фундаментальные структуры данных в информатике, используемые в широком спектре приложений. Понимание принципов их работы и способов реализации поможет вам решать сложные задачи более эффективно и элегантно.

В этой статье мы рассмотрим основы бинарных деревьев, включая их структуру, вставку и удаление узлов, обход и поиск элементов. Мы также приведем практические примеры на языке программирования C , чтобы вы могли увидеть, как эти концепции применяются на практике.

Итак, приступим!

Что такое бинарные деревья?

Бинарные деревья — это иерархические структуры данных, состоящие из взаимосвязанных узлов. Каждый узел может иметь до двух дочерних узлов: один слева и один справа. Именно эта структура с двумя ветвями отличает двоичные деревья от других структур данных.

В двоичном дереве первый узел называется корневым узлом. Дочерние узлы называются дочерними узлами, а узлы без дочерних узлов называются листовыми узлами. Узлы на одном уровне называются родственными узлами.

Преимущества бинарных деревьев

Двоичные деревья обладают рядом преимуществ с точки зрения эффективного хранения и поиска данных. Некоторые из основных преимуществ включают в себя:

  1. эффективный поискДвоичные деревья позволяют осуществлять поиск элементов во время выполнения быстрее, чем другие структуры данных, такие как связанные списки. Это связано с иерархической структурой дерева и его способностью быстро разбивать набор данных.
  2. Гибкая вставка и удалениеДвоичные деревья легко адаптируются к операциям вставки и удаления узлов. В отличие от статических структур данных, таких как массивы, двоичные деревья могут динамически расти и изменять свою структуру.
  3. Представление иерархических отношенийБинарные деревья особенно полезны для представления иерархических отношений между элементами. Например, в структуре каталогов файлов каждый каталог может быть представлен как узел в дереве, а подкаталоги и файлы — как его дочерние узлы.

Структура бинарного дерева

Прежде чем углубиться в реализацию бинарных деревьев на языке C, важно понять их базовую структуру. Каждый узел в двоичном дереве содержит значение и ссылки на свои левые и правые дочерние узлы, если таковые имеются.

В следующей таблице показана структура узла в двоичном дереве:

Двоичный узел
значение
Левый узел
Правый узел

Каждый узел может хранить любой тип данных, например целые числа, символы или более сложные структуры. Корневой узел является начальной точкой дерева, и из него мы можем получить доступ ко всем остальным узлам.

Реализация бинарных деревьев на языке C

Теперь, когда мы получили базовое представление о бинарных деревьях, пришло время реализовать их в языке программирования C. Далее мы рассмотрим, как объявить и использовать структуру бинарного дерева в C.

Декларация структуры двоичного дерева

В языке C мы можем объявить структуру двоичного дерева, используя структуру и указатели. Вот базовое описание структуры:

struct NodoArbol {
    int valor;
    struct NodoArbol* izquierdo;
    struct NodoArbol* derecho;
};

В этой структуре valor представляет собой значение, хранящееся в узле, и izquierdo y derecho являются указателями на левый и правый дочерние узлы соответственно.

  Живой интеллект: что это такое, как он работает и почему он важен

Создание нового узла

Чтобы создать новый узел в двоичном дереве, нам необходимо выделить память для узла и задать его значения. Вот функция C, которая создает новый узел:

struct NodoArbol* crearNodo(int valor) {
    struct NodoArbol* nodo = (struct NodoArbol*)malloc(sizeof(struct NodoArbol));
    nodo->valor = valor;
    nodo->izquierdo = NULL;
    nodo->derecho = NULL;
    return nodo;
}

Функция malloc Используется для выделения динамической памяти узлу. Затем мы устанавливаем значения узлов и возвращаем созданный узел.

Вставка узлов

Вставка узлов — фундаментальный процесс в бинарных деревьях. Позволяет добавлять новые элементы в дерево в правильном положении на основе значения узла. Ниже представлена ​​функция C для вставки узла в двоичное дерево:

struct NodoArbol* insertarNodo(struct NodoArbol* raiz, int valor) {
    if (raiz == NULL) {
        return crearNodo(valor);
    }

    if (valor < raiz->valor) {
        raiz->izquierdo = insertarNodo(raiz->izquierdo, valor);
    } else if (valor > raiz->valor) {
        raiz->derecho = insertarNodo(raiz->derecho, valor);
    }

    return raiz;
}

Эта функция получает указатель на корень дерева и значение узла для вставки. Если корень равен нулю, это означает, что дерево пусто, и мы создаем новый узел в корне. В противном случае мы сравниваем значение узла со значением корня и решаем, вставлять ли узел слева или справа.

Удаление узлов

Удаление узлов в двоичном дереве может оказаться немного сложнее. Это зависит от нескольких случаев, например, от того, есть ли у удаляемого узла дочерние элементы или нет. Ниже представлена ​​функция C для удаления узла в двоичном дереве:

struct NodoArbol* eliminarNodo(struct NodoArbol* raiz, int valor) {
    if (raiz == NULL) {
        return raiz;
    }

    if (valor < raiz->valor) {
        raiz->izquierdo = eliminarNodo(raiz->izquierdo, valor);
    } else if (valor > raiz->valor) {
        raiz->derecho = eliminarNodo(raiz->derecho, valor);
    } else {
        if (raiz->izquierdo == NULL) {
            struct NodoArbol* temp = raiz->derecho;
            free(raiz);
            return temp;
        } else if (raiz->derecho == NULL) {
            struct NodoArbol* temp = raiz->izquierdo;
            free(raiz);
            return temp;
        }

        struct NodoArbol* sucesor = encontrarSucesor(raiz->derecho);
        raiz->valor = sucesor->valor;
        raiz->derecho = eliminarNodo(raiz->derecho, sucesor->valor);
    }

    return raiz;
}

В этой функции мы проверяем, является ли значение узла меньшим, большим или равным значению текущего корня. В зависимости от случая мы осуществляем следующие действия:

  • Если значение меньше, то переходим в левую часть дерева.
  • Если значение больше, то переходим вправо по дереву.
  • Если значение равно, мы находим ближайшего преемника узла (наименьший узел в правом поддереве) и заменяем его текущим узлом. Затем удаляем преемника из правого поддерева.

Обходы в бинарных деревьях

Обходы — это операции, которые позволяют нам посещать все узлы двоичного дерева в определенном порядке. Существует три распространенных типа туров:

Обход в порядке возрастания : сначала посещается левое поддерево, затем текущий узел и, наконец, правое поддерево. Вот функция на языке C, которая выполняет обход бинарного дерева в порядке возрастания:

void inOrden(struct NodoArbol* raiz) {
    if (raiz != NULL) {
        inOrden(raiz->izquierdo);
        printf("%d ", raiz->valor);
        inOrden(raiz->derecho);
    }
}

Обход в прямом порядке : сначала посещается текущий узел, затем левое поддерево и, наконец, правое поддерево. Вот функция на языке C, которая выполняет обход бинарного дерева в прямом порядке:

void preOrden(struct NodoArbol* raiz) {
    if (raiz != NULL) {
        printf("%d ", raiz->valor);
        preOrden(raiz->izquierdo);
        preOrden(raiz->derecho);
    }
}

Обход в обратном порядке : сначала посещается левое поддерево, затем правое поддерево и, наконец, текущий узел. Вот функция на языке C, которая выполняет обход бинарного дерева в обратном порядке:

void postOrden(struct NodoArbol* raiz) {
    if (raiz != NULL) {
        postOrden(raiz->izquierdo);
        postOrden(raiz->derecho);
        printf("%d ", raiz->valor);
    }
}

Поиск элементов

Поиск элементов в двоичном дереве позволяет быстро находить определенное значение в структуре данных. Вот функция C для поиска элемента в двоичном дереве:

struct NodoArbol* buscarElemento(struct NodoArbol* raiz, int valor) {
    if (raiz == NULL || raiz->valor == valor) {
        return raiz;
    }

    if (valor < raiz->valor) {
        return buscarElemento(raiz->izquierdo, valor);
    } else {
        return buscarElemento(raiz->derecho, valor);
    }
}

Эта функция выполняет рекурсивный поиск в двоичном дереве. Если значение текущего узла равно искомому значению, узел возвращается. В противном случае выполняется поиск левого или правого поддерева на основе значения, и процесс повторяется до тех пор, пока значение не будет найдено или не будет достигнут нулевой узел.

  Алгоритмы прямого перебора в программировании: что это такое, примеры и отличия от поиска с возвратом.

Примеры реализации бинарных деревьев на языке C

Теперь, когда мы рассмотрели основы бинарных деревьев и способы их реализации на языке C, давайте рассмотрим несколько практических примеров.

Пример 1: Создание бинарного дерева

Предположим, мы хотим создать двоичное дерево со следующими значениями: 10, 5, 15, 3, 7, 13, 18. Вот как это можно сделать на языке C:

int main() {
    struct NodoArbol* raiz = NULL;

    raiz = insertarNodo(raiz, 10);
    raiz = insertarNodo(raiz, 5);
    raiz = insertarNodo(raiz, 15);
    raiz = insertarNodo(raiz, 3);
    raiz = insertarNodo(raiz, 7);
    raiz = insertarNodo(raiz, 13);
    raiz = insertarNodo(raiz, 18);

    return 0;
}

В этом примере мы создаем указатель на корень дерева, а затем используем функцию insertarNodo для добавления значений в дерево.

Пример 2: Упорядоченный обход бинарного дерева

Чтобы вывести значения двоичного дерева по порядку, мы можем вызвать функцию inOrden от безопасной манеры:

int main() {
    // Crear el árbol binario

    printf("Recorrido en orden: ");
    inOrden(raiz);
    printf("\n");

    return 0;
}

В этом примере значения в дереве будут выведены в порядке возрастания.

Часто задаваемые вопросы

1. В чем разница между бинарным деревом и бинарным деревом поиска?

Двоичное дерево поиска (BST) — это особый тип двоичного дерева, в котором элементы расположены таким образом, что меньшие значения находятся слева, а большие — справа. Это позволяет осуществлять более эффективный поиск элементов по сравнению с обычным бинарным деревом.

2. Могут ли в двоичном дереве быть узлы с повторяющимися значениями?

Да, в двоичном дереве могут быть узлы с повторяющимися значениями. Однако в зависимости от реализации и конкретных правил двоичного дерева могут существовать разные способы обработки дублирующихся узлов. Некоторые реализации могут допускать дубликаты и хранить их в любом порядке, в то время как другие могут требовать, чтобы дублирующиеся значения обрабатывались особым образом или отбрасывались.

3. Как удалить определенный узел из двоичного дерева?

Чтобы удалить определенный узел из двоичного дерева, необходимо выполнить следующие действия:

  1. Найдите узел, который вы хотите удалить, с помощью поиска по дереву.
  2. Рассмотрим различные случаи исключения:
    • Если у узла нет дочерних элементов, вы можете просто удалить его и освободить его память.
    • Если у узла есть только один дочерний узел, вы можете заменить узел его дочерним узлом.
    • Если у узла два потомка, необходимо найти ближайшего преемника (наименьший узел в правом поддереве) и заменить значение удаляемого узла на значение преемника. Затем удалите преемника из дерева.
  3. При необходимости корректирует ссылки и указатели для поддержания правильной структуры дерева.
  5 частей алгоритма программирования

4. Что такое полное двоичное дерево?

Полное двоичное дерево — особый тип двоичного дерева, в котором все уровни, за исключением, возможно, последнего, полностью заполнены, а узлы последнего уровня расположены максимально левее. Это означает, что все узлы имеют двух дочерних элементов, за исключением, возможно, узлов на последнем уровне, которые могут иметь одного или ни одного дочернего элемента.

5. Какова высота двоичного дерева?

Высота бинарного дерева — это длина самого длинного пути от корня до листа. Другими словами, это максимальное количество ребер между корнем и любым листом дерева. Высота измеряется в количестве уровней, поэтому дерево с одним узлом имеет высоту 0, а пустое дерево не имеет высоты.

6. Когда следует использовать двоичное дерево в своих программах?

Двоичные деревья полезны в различных ситуациях. Вот некоторые распространенные случаи, когда можно использовать двоичные деревья:

  • Эффективный поиск элементов: если вам необходимо быстро найти элементы в структуре данных, двоичное дерево может обеспечить эффективный доступ к данным.
  • Представление иерархических отношений: двоичные деревья идеально подходят для представления иерархических отношений, таких как структура каталогов в файловая система.
  • Сортировка данных: вы можете использовать двоичные деревья поиска для эффективной сортировки данных и выполнения поиска, вставки и удаления за логарифмическое время.

Не забудьте оценить свои требования и учесть сложность операций с двоичными деревьями, прежде чем принять решение об их использовании в своих программах.

Заключение

В этом подробном руководстве мы рассмотрели основные концепции бинарных деревьев в языке C. Мы узнали об их структуре, о том, как вставлять и удалять узлы, выполнять обходы и искать элементы в бинарном дереве.

Мы надеемся, что это руководство дало вам четкое представление о двоичных деревьях и о том, как их реализовать на языке C. Двоичные деревья — это универсальные и мощные структуры данных, которые могут помочь вам решить широкий спектр задач в программировании.

Не забывайте практиковаться и экспериментировать с предоставленными примерами, чтобы закрепить свое понимание бинарных деревьев в языке C. Удачи вам в изучении и разработке программного обеспечения!