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

323

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

Jichao
источник

Ответы:

425

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

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

  • Двоичное дерево - используется в многих поисковых приложениях , где данные постоянно на входе / выходе, такие , как mapи setобъектах в библиотеках многих Языков.
  • Раздел двоичного пространства - используется почти в каждой трехмерной видеоигре, чтобы определить, какие объекты необходимо визуализировать.
  • Двоичные попытки. Используются практически в каждом маршрутизаторе с высокой пропускной способностью для хранения таблиц маршрутизатора.
  • Хеш-деревья - используются в p2p-программах и специализированных графических сигнатурах, в которых необходимо проверять хеш, но весь файл недоступен.
  • Кучи - Используются для реализации эффективных очередей приоритетов, которые, в свою очередь, используются для планирования процессов во многих операционных системах, качества обслуживания в маршрутизаторах и A * (алгоритм поиска пути, используемый в приложениях AI, включая робототехнику и видеоигры) , Также используется в кучи-сортировке.
  • Huffman Coding Tree ( Chip Uni ) - используется в алгоритмах сжатия, таких как файлы форматов .jpeg и .mp3.
  • GGM Trees - используется в криптографических приложениях для генерации дерева псевдослучайных чисел.
  • Синтаксическое дерево - построено компиляторами и (неявно) калькуляторами для анализа выражений.
  • Treap - рандомизированная структура данных, используемая в беспроводных сетях и распределении памяти.
  • T-дерево - Хотя большинство баз данных использует некоторую форму 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-арные деревья по-прежнему полезны в нишевых ситуациях. Примерами, которые сразу приходят на ум, являются квад-деревья и другие деревья с разделением пространства, где разделение пространства с использованием только двух узлов на уровень сделает логику излишне сложной; B-деревья используются во многих базах данных, где ограничивающим фактором является не количество сравнений, выполняемых на каждом уровне, а количество узлов, которые можно загрузить с жесткого диска одновременно)

BlueRaja - Дэнни Пфлугхофт
источник
3
> Treap - рандомизированная структура данных, используемая в беспроводных сетях и распределении памяти. Как именно они используются в распределении памяти и беспроводных сетях?
FRP
1
Есть много полезных структур данных и алгоритмов, которые используют слово «двоичный», и «двоичное дерево поиска» на самом деле является одним из них, но это не тот вопрос, который был задан. Какая польза от простого старого «бинарного дерева»: не отсортированного, не сбалансированного, не полного. Просто старое случайное дерево?
Майкл Эриксон,
4
@MichaelErickson Ты читал ответ? Потому что это именно тот вопрос, на который я ответил.
BlueRaja - Дэнни Пфлугхофт
1
Я полагаю, что хеш-деревья обычно называют деревьями Меркле, по крайней мере, в сообществах биткойнов и эфириума, IPFS и т. Д.
Duke
1
Жаль, что этот ответ содержит так много ошибок. n-арные деревья на современном оборудовании почти всегда предпочтительнее двоичных деревьев. Названные приложения в основном не используют двоичные деревья.
Стефан Эггермонт
290

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

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

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

Alice
Bob
Chloe
David
Edwina
Frank

так что при чтении обратно получилось следующее дерево:

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

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

Двоичные деревья становятся действительно полезными для поиска, когда вы их балансируете. Это включает в себя вращение поддеревьев через их корневой узел, так что разница высот между любыми двумя поддеревьями меньше или равна 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
  /     \          /     \      /     \
 =       =        =       =    =       =

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

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

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

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

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

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

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

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

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

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


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

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

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

paxdiablo
источник
28
+1 За такой мы письменный ответ; плюс знакомство со сбалансированными многоходовыми деревьями, с чем я раньше не сталкивался.
Тони
3
Я не согласен с вашим утверждением о том, что они полезны для немногих, кроме обучения студентов. Они весьма полезны даже в качестве простой статической структуры данных. Тем не менее, это очень хорошо написанный и иллюстрированный ответ, так что +1 для всех остальных. :-)
Бенсон
1
На современном оборудовании почти все деревья должны быть многоходовыми.
Стефан Эггермонт
89

Организация азбуки Морзе представляет собой двоичное дерево.

бинарное дерево

азбука Морзе

IliasT
источник
4
Это мой любимый ответ. Непосредственно иллюстрирует снижение вычислительной сложности, необходимой для достижения символов дальше по списку.
Усы
7
Мне очень понравился этот ответ!
Дункан Эдвардс
2
Раньше я работал в компании, которая производила фильтры для радиолюбителей, это
уводило
62

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

Бинарное дерево

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

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

Этот поиск показан ниже: Поиск дерева

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

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

Удалить 1

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

Удалить 3

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

Удалить 4


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

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

введите описание изображения здесь

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

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

Drise
источник
5
Вам нужно искать только O (log n), если вы имеете дело с двоичным деревом поиска (которое само по себе хорошо сбалансировано). Произвольные двоичные деревья не имеют ограничений по порядку, а случайный BST имеет сложность поиска O (log h ).
dlev
Это не те виды, которые хранятся в соответствующих стандартных контейнерах.
Щенок
12

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

BlueRaja - Дэнни Пфлугхофт
источник
1
Бинарные деревья поиска - это не приложение, а особый тип бинарного дерева.
nbro
@nbro: Вы спорите о бессмысленной семантике, это оба правильные способы сказать одно и то же. Обратите внимание, что «приложение» здесь не означает то же самое, что и «компьютерное приложение»
BlueRaja - Дэнни Пфлугхофт
Я думаю, что вопрос был больше связан с реальными приложениями, а не с конкретными реализациями или конкретными типами бинарных деревьев. И, между прочим, спрашивающий не спрашивает, какие структуры данных являются конкретными двоичными деревьями. Это не бессмысленно, ИМО. Но я согласен, что это все равно неоднозначно. Например, в своем другом ответе вы упоминаете синтаксические деревья, которые являются приложением древовидной (но не обязательно двоичной ) структуры данных в реальном приложении. Исходя из ваших рассуждений, я мог бы перечислить все известные мне двоичные деревья, мы все были бы счастливы из-за количества элементов.
nbro
11

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

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

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

    *
   / \
  +   2
 / \
1   3

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

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

Бестолковые
источник
9

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

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

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

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

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

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

Джордж
источник
9

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

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

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

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

mloskot
источник
7

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

Инь Чжу
источник
2
фактически в C ++ наборы / карты чаще всего основаны на красно-черных деревьях, которые представляют собой двоичное дерево поиска с парой дополнительных ограничений.
Идан К
6

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

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

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

Рохит
источник
5

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

Аарон
источник
2

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

Anycorn
источник
2

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

Стефан Эггермонт
источник
2
Субоптимальный по сравнению с чем?
многоходовые деревья. Линейный поиск по всем данным, которые вы получаете из одного доступа к памяти, намного быстрее, чем новый доступ к основной памяти
Stephan Eggermont
Я хочу верить вам, но в вашем ответе нет ничего, что подкрепляло бы ваши заявления. Источники, Большая запись или что-то. Пожалуйста, дополните.
PhilT
@PhilT en.wikipedia.org/wiki/CPU_cache
Стефан Эггермонт
0

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

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

evenhorizon
источник