Когда временная сложность поиска в бинарном дереве поиска (BST) становится не логарифмической?

Ответ

Сложность поиска в базовом бинарном дереве поиска (Binary Search Tree, BST) составляет O(h), где h — высота дерева. Логарифмическая сложность O(log n) гарантируется только для сбалансированных деревьев (как в std::map, реализованном красно-черным деревом).

Сложность деградирует до линейной O(n) в случае вырождения дерева в связный список. Это происходит при:

  1. Последовательной вставке отсортированных (или почти отсортированных) данных. Базовый алгоритм вставки BST помещает большие значения в правого ребенка, что при отсортированной последовательности создает "вертикальную" структуру.

    // Вырожденное дерево после вставки 1, 2, 3, 4, 5
    //    1
    //     
    //      2
    //       
    //        3
    //         
    //          4
    //           
    //            5
    // Поиск числа 5 потребует 5 сравнений (O(n)).
  2. Использовании BST без механизмов самобалансировки (например, наивная реализация для обучения).

Решение: Использование сбалансированных деревьев поиска, которые автоматически поддерживают высоту ~O(log n):

  • AVL-деревья: Строгая балансировка, быстрый поиск.
  • Красно-черные деревья (используются в std::map, std::set): Менее строгая балансировка, но меньше операций поворота при модификациях.
  • B-деревья, Splay-деревья и др.

В C++ контейнеры std::set и std::map гарантируют логарифмическую сложность операций, так как реализованы как красно-черные деревья.

Ответ 18+ 🔞

Да ты посмотри, какая история интересная вырисовывается! Сидишь ты такой, пишешь свою структуру данных, дерево бинарного поиска, красота. Всё вроде работает, поиск за O(log n), жизнь прекрасна. А потом ёпта, вставляешь данные по порядку — 1, 2, 3, 4, 5 — и дерево твоё, блядь, накрывается медным тазом.

Превращается в обычный, ебаный, связный список! Овердохуища! Высота h становится равна n, и поиск элемента, который в хвосте висит, требует пройтись по всем узлам. Сложность из логарифмической O(log n) деградирует до линейной O(n). Сам от себя охуеешь, когда поймёшь, что твоё "дерево" — это просто цепочка.

// Вот во что превращается красота после вставки 1, 2, 3, 4, 5
//    1
//     
//      2
//       
//        3
//         
//          4
//           
//            5
// Чтобы найти пятёрку, надо пройти через все узлы. Пиздец, да?

И главная хитрая жопа тут в том, что для наивного BST это норма! Оно так и работает. Если кормить его отсортированной последовательностью, оно с радостью выродится в список. Доверия ебать ноль к такой структуре в продакшене.

Так что же делать? Э, бошка, думай! Нельзя использовать голое, небалансированное дерево для серьёзных дел. Нужны структуры, которые сами себя поддерживают в адекватном состоянии.

Решение — ебать копать — использовать сбалансированные деревья поиска. Они как умные, следят за собой:

  • AVL-деревья: Перфекционисты, балансируют строго, поиск супербыстрый.
  • Красно-чёрные деревья (как в std::map и std::set): Пофигисты помягче, балансируют не так жёстко, но зато меньше мороки при вставке-удалении.
  • Есть ещё куча других: B-деревья, Splay-деревья — ёперный театр вариантов.

Короче, вывод простой, чувак: в C++ для таких задач бери std::set или std::map и не парься. Они внутри — красно-чёрные деревья, и гарантируют тебе тот самый сладкий O(log n) на операциях. А свою учебную реализацию BST можешь смело выкидывать в пизду при первой же встрече с реальными данными.