Каковы приложения бинарных деревьев?

Мне интересно, каковы конкретные применения бинарных деревьев. Не могли бы вы привести некоторые реальные примеры?

Ответ 1

Чтобы ссориться о производительности двоичных деревьев не имеет смысла - они не являются структурой данных, а представляют собой семейство структур данных, все с различными характеристиками производительности. Хотя верно, что несбалансированные бинарные деревья выполняют гораздо хуже, чем самобалансирующиеся двоичные деревья для поиска, существует множество двоичных деревьев (например, двоичные попытки), для которых "балансировка" не имеет значения.

Приложения двоичных деревьев

  • Дерево двоичного поиска - используется во многих поисковых приложениях, где данные постоянно входят/оставляются, например объекты map и set в библиотеках многих языков.
  • Binary Space Partition - Используется почти в каждой 3D-видеоигре, чтобы определить, какие объекты нужно визуализировать.
  • Binary Tries - используется почти для каждого маршрутизатора с высокой пропускной способностью для хранения таблиц маршрутизаторов.
  • Hash Trees - используется в программах p2p и специализированных подписях изображений, в которых необходимо проверить хэш, но весь файл недоступен.
  • Heaps - используется для реализации эффективных приоритетных очередей, которые, в свою очередь, используются для планирования процессов во многих операционных системах, Quality-of-Service в маршрутизаторах и A * (алгоритм поиска пути, используемый в приложениях AI, включая робототехнику и видеоигры). Также используется в куче-сортировке.
  • Дерево кодирования Хаффмана (Chip Uni) - используется в алгоритмах сжатия, таких как как те, которые используются файлами формата .jpeg и .mp3.
  • Деревья GGM - Используется в криптографических приложениях для генерации дерева псевдослучайных чисел.
  • Дерево синтаксиса - Создано компиляторами и (неявно) калькуляторами для разбора выражений.
  • Treap - Рандомизированная структура данных, используемая в беспроводной сети и распределении памяти.
  • T-tree - Хотя большинство баз данных используют некоторую форму B-дерева для хранения данных на диске, базы данных, которые хранят все (большинство) их данные в памяти часто используют T-деревья для этого.

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

В (сбалансированном) бинарном дереве с узлами m, перемещение от одного уровня к другому требует одного сравнения, и есть уровни log_2(m) для общего количества сравнений log_2(m).

Напротив, для n-арного дерева требуется сравнение log_2(n) (с использованием бинарного поиска) для перехода на следующий уровень. Поскольку существует log_n(m) общих уровней, для поиска требуется log_2(n)*log_n(m)= log_2(m) общее количество сравнений. Таким образом, хотя n-арные деревья более сложны, они не дают никакого преимущества с точки зрения общего количества необходимых сравнений.

(Тем не менее, n-арные деревья по-прежнему полезны в нишевых ситуациях. Примерами, которые сразу приходят в голову, являются quad-trees и другие деревья разделения пространства, где разделение пространства с использованием только двух узлов на уровень сделает логику излишне сложной, а B-tree используется во многих базах данных, где ограничивающим фактором является не то, сколько сравнений выполняется на каждом уровне, но сколько узлов может быть загружено с жесткого диска сразу)

Ответ 2

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

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

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

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

Alice
Bob
Chloe
David
Edwina
Frank

чтобы при чтении его в итоге появилось следующее дерево:

  Alice
 /     \
=       Bob
       /   \
      =     Chloe
           /     \
          =       David
                 /     \
                =       Edwina
                       /      \
                      =        Frank
                              /     \
                             =       =

которая является вырожденной формой. Если вы ищете Фрэнка в этом дереве, вам придется искать все шесть узлов, прежде чем вы его найдете.

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

1.   Alice
    /     \
   =       =

 

2.   Alice
    /     \
   =       Bob
          /   \
         =     =

 

3.        Bob
        _/   \_
   Alice       Chloe
  /     \     /     \
 =       =   =       =

 

4.        Bob
        _/   \_
   Alice       Chloe
  /     \     /     \
 =       =   =       David
                    /     \
                   =       =

 

5.           Bob
        ____/   \____
   Alice             David
  /     \           /     \
 =       =     Chloe       Edwina
              /     \     /      \
             =       =   =        =

 

6.              Chloe
            ___/     \___
         Bob             Edwina
        /   \           /      \
   Alice     =      David        Frank
  /     \          /     \      /     \
 =       =        =       =    =       =

Фактически вы можете видеть, что все под деревья, вращающиеся влево, по мере добавления записей, и это дает вам сбалансированное двоичное дерево, в котором наихудший поиск выглядит как O (log N), а не O (N) как вырожденная форма дает. Ни в коем случае наивысший NULL (=) не отличается от самого низкого уровня более чем на один уровень. И в последнем дереве вы можете найти Фрэнка, только глядя на три узла (Chloe, Edwina и, наконец, Frank).

Конечно, они становятся еще более полезными, когда вы делаете их сбалансированными многодорожечными деревьями, а не двоичными. Это означает, что каждый node содержит более одного элемента (технически они содержат N элементов и N + 1 указателей, двоичное дерево является частным случаем одностороннего многодорожечного дерева с 1 элементом и двумя указателями).

С трехсторонним деревом вы получите:

  Alice Bob Chloe
 /     |   |     \
=      =   =      David Edwina Frank
                 /     |      |     \
                =      =      =      =

Обычно это используется для поддержания ключей для индекса элементов. Я написал программное обеспечение базы данных, оптимизированное для аппаратного обеспечения, где node - это точно размер блока диска (скажем, 512 байт), и вы помещаете столько ключей, сколько можете, в один node. Указатели в этом случае были фактически номерами записей в файле прямого доступа с фиксированной длиной записи, отдельно от файла индекса (поэтому номер записи X можно найти, просто обратившись к X * record_length).

Например, если указатели имеют 4 байта и размер ключа равен 10, количество ключей в 512-байтовом node равно 36. Это 36 ключей (360 байт) и 37 указателей (148 байт) для всего 508 байт с 4 байтами в расчете на node.

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

Я не вижу причин делать это для структуры в памяти, вам будет лучше придерживаться сбалансированного двоичного дерева и поддерживать простой код.

Также имейте в виду, что преимущества O (log N) над O (N) действительно не появляются, когда ваши наборы данных малы. Если вы используете многодорожечное дерево для хранения пятнадцати человек в своей адресной книге, это, вероятно, будет излишним. Преимущества приходят, когда вы храните что-то вроде каждого заказа от своих сотен тысяч клиентов за последние десять лет.

Вся значимость нотации Big-O заключается в том, чтобы указать, что происходит, когда N приближается к бесконечности. Некоторые люди могут не согласиться, но вполне нормально использовать сортировку пузырьков, если вы уверены, что наборы данных останутся ниже определенного размера, если ничего другого не будет доступно: -)


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

  • Двоичные кучи, где более высокие ключи выше или равны нижним, а не слева от (или ниже или равно и справа);
  • Хэш-деревья, похожие на хэш-таблицы;
  • Абстрактные синтаксические деревья для компиляции компьютерных языков;
  • Деревья Хаффмана для сжатия данных;
  • Маршрутизация деревьев для сетевого трафика.

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

Ответ 3

Двоичное дерево представляет собой структуру данных дерева, в которой каждый node имеет не более двух дочерних узлов, обычно выделяемых как "левые" и "правые". Узлы с дочерними элементами являются родительскими узлами, а дочерние узлы могут содержать ссылки на своих родителей. За пределами дерева часто упоминается "корень" node (предок всех узлов), если он существует. Любые node в структуре данных могут быть достигнуты, начиная с root node и повторно следуя ссылкам на левый или правый дочерний элемент. В двоичном дереве степень каждого node равна максимум двум.

Binary Tree

Двоичные деревья полезны, потому что, как вы можете видеть на картинке, если вы хотите найти какой-либо node в дереве, вам нужно будет всего 6 раз. Например, если вы хотите найти node 24, вы должны начать с корня.

  • Корень имеет значение 31, которое больше 24, поэтому вы переходите влево node.
  • Левое node имеет значение 15, что меньше 24, поэтому вы переходите вправо node.
  • Правило node имеет значение 23, которое меньше 24, поэтому вы переходите вправо node.
  • Правило node имеет значение 27, которое больше 24, поэтому вы переходите влево node.
  • Левое node имеет значение 25, что больше 24, поэтому вы переходите влево node.
  • node имеет значение 24, это ключ, который мы ищем.

Этот поиск показан ниже: Tree search

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

Удаления могут стать сложными. Если node имеет 0 или 1 дочерний элемент, то это просто вопрос перемещения некоторых указателей, чтобы исключить тот, который нужно удалить. Однако вы не можете легко удалить node с двумя детьми. Поэтому мы берем короткое сокращение. Скажем, мы хотели удалить node 19.

Delete 1

Так как попытка определить, куда перемещать левый и правый указатели, это непросто, мы находим его подстановкой. Мы идем к левому поддереву и идем так далеко, как можем. Это дает нам следующее наибольшее значение node, которое мы хотим удалить.

Delete 3

Теперь мы копируем все 18 содержимого, за исключением левого и правого указателей, и удалим исходный 18 node.

Delete 4


Чтобы создать эти изображения, я реализовал дерево AVL, собственное балансировочное дерево, так что в любой момент времени дерево имеет не более одного уровня разницы между листовыми узлами (узлы без детей). Это препятствует тому, чтобы дерево искажалось и поддерживало максимальное время поиска O(log n), при этом затраты на добавление и удаление потребовали немного больше времени.

Вот пример, показывающий, как мое дерево AVL сохраняет себя как можно более компактным и сбалансированным.

enter image description here

В отсортированном массиве поиск по-прежнему будет принимать O(log(n)), точно так же, как дерево, но случайная вставка и удаление вместо дерева O(log(n)) будет принимать O (n). Некоторые STL-контейнеры используют эти характеристики производительности в своих интересах, поэтому время вставки и удаления занимает максимум O(log n), что очень быстро. Некоторые из этих контейнеров: map, multimap, set и multiset.

Пример кода для дерева AVL можно найти на http://ideone.com/MheW8

Ответ 4

Организация Морзе код является двоичным деревом.

binary-tree

morse-code

Ответ 5

Ответ 6

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

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

Например, выражение (1+3)*2 может быть выражено как:

    *
   / \
  +   2
 / \
1   3

Чтобы оценить выражение, мы запрашиваем значение родительского элемента. Этот node в свою очередь получает свои значения от своих дочерних элементов, плюс оператор и node, который просто содержит "2". Оператор плюс в свою очередь получает свои значения от детей со значениями "1" и "3" и добавляет их, возвращая 4 в умножение node, которое возвращает 8.

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

Ответ 7

Основным приложением является деревья двоичного поиска. Это структура данных, в которой поиск, вставка и удаление выполняются очень быстро (около log(n) операций)

Ответ 8

Одним из наиболее распространенных приложений является эффективное хранение данных в отсортированной форме для быстрого доступа и поиска сохраненных элементов. Например, std::map или std::set в стандартной библиотеке С++.

Двоичное дерево как структура данных полезно для различных реализаций парсеров выражения и решателей выражений.

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

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

Ответ 9

Я не думаю, что для "чистых" двоичных деревьев существует какая-либо польза. (за исключением образовательных целей) Сбалансированные двоичные деревья, такие как Красно-черные деревья или Деревья AVL гораздо полезнее, поскольку они гарантируют операции O (logn). Обычные двоичные деревья могут оказаться списком (или почти списком) и не очень полезны в приложениях с большим количеством данных.

Сбалансированные деревья часто используются для реализации карт или наборов. Они также могут использоваться для сортировки в O (nlogn), даже если существуют лучшие способы сделать это.

Также для поиска/вставки/удаления могут использоваться хэш-таблицы, которые обычно имеют лучшую производительность, чем бинарные деревья поиска (сбалансированные или нет).

Приложение, в котором (сбалансированные) двоичные деревья поиска были бы полезны, было бы полезно, если бы искали/вставляли/удаляли и сортировали. Сортировка может быть на месте (почти, игнорируя пространство стека, необходимое для рекурсии), учитывая готовое построение сбалансированного дерева. Он по-прежнему будет O (nlogn), но с меньшим постоянным коэффициентом и не требует дополнительного пространства (кроме нового массива, предполагая, что данные должны быть помещены в массив). С другой стороны, хэш-таблицы нельзя сортировать (по крайней мере, не напрямую).

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

Другие деревья, такие как f.e. B+trees широко используются в базах данных

Ответ 11

В С++ STL и многие другие стандартные библиотеки на других языках, таких как Java и С#. Двоичные деревья поиска используются для реализации набора и карты.

Ответ 12

Одним из наиболее важных приложений двоичных деревьев являются сбалансированные двоичные деревья поиска, такие как:

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

В связи с этим общая высота дерева остается порядка log n, а операции, такие как поиск, вставка и удаление узлов, выполняются в O (log n) времени. STL С++ также реализует эти деревья в виде множеств и карт.

Ответ 13

Они могут использоваться как быстрый способ сортировки данных. Вставьте данные в двоичное дерево поиска в O (log (n)). Затем пересечь дерево, чтобы отсортировать их.

Ответ 14

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

Ответ 15

Реализации java.util.Set

Ответ 16

На современном оборудовании двоичное дерево почти всегда субоптимально из-за плохого кэша и поведения пространства. Это также относится к (полу) сбалансированным вариантам. Если вы их найдете, то там, где производительность не учитывается (или доминирует функция сравнения), или, более вероятно, по историческим причинам или причинам невежества.

Ответ 17

Компилятор, который использует двоичное дерево для представления AST, может использовать известные алгоритмы для разбор дерева как постопера, inorder.The программисту не нужно придумывать его собственный алгоритм. Поскольку двоичное дерево для исходного файла выше, чем n-арное дерево, это здание занимает больше времени. Возьмите это производство: selstmnt: = "if" "(" expr ")" stmnt "ELSE" stmnt В двоичном дереве он будет иметь 3 уровня узлов, но n-арное дерево будет иметь 1 уровень (чидов)

Вот почему ОС на базе Unix работает медленно.