Чому C ++ STL не надає жодних «деревних» контейнерів?


373

Чому C ++ STL не надає жодних "деревних" контейнерів, і що найкраще використовувати замість цього?

Я хочу зберігати ієрархію об’єктів як дерево, а не використовувати дерево як підвищення продуктивності ...


7
Мені потрібно дерево для зберігання подання ієрархії.
Родді

20
Я з хлопцем, який вниз проголосував за "правильні" відповіді, що, здається, є; «Дерева марні». Існують важливі значення, якщо дерева незрозумілі.
Joe Soul-donosi

Я думаю, що причина банальна - ще ніхто не реалізував її у стандартній бібліотеці. Це як би стандартної бібліотеки не було std::unordered_mapі std::unordered_setдо недавнього часу. А до цього контейнерів STL взагалі не було в стандартній бібліотеці.
doc

1
Мої думки (хоча я ніколи не читав відповідний стандарт, отже, це коментар, а не відповідь) - це те, що STL не переймається конкретними структурами даних, він піклується про специфікації щодо складності та те, які операції підтримуються. Таким чином, використовувана базова структура може змінюватись між реалізаціями та / або цільовими архітектурами, за умови, що вона відповідає специфікаціям. Я майже впевнений, std::mapі я std::setбуду використовувати дерево у кожній реалізації там, але вони не повинні, якщо якась недеревна структура також відповідає специфікаціям.
Марк К Коуан

Відповіді:


182

Ви можете скористатися двома причинами:

Ви хочете відобразити проблему за допомогою деревоподібної структури:
для цього у нас є бібліотека збільшених графіків

Або ви хочете контейнер, який має такі характеристики дерева, як для цього

В основному характеристики цих двох контейнерів такі, що їх практично доводиться реалізовувати за допомогою дерев (хоча це насправді не є вимогою).

Дивіться також це питання: Впровадження дерева C


64
Є багато, багато причин використовувати дерево, навіть якщо вони є найпоширенішими. Найпоширеніші! Рівні всі.
Joe Soul-donosi

3
Третя основна причина бажати дерево - це завжди відсортований список із швидкою вставкою / видаленням, але для цього є std: multiset.
VoidStar

1
@Durga: Не впевнений, наскільки глибина відповідна, коли ви використовуєте карту як відсортований контейнер. Карта гарантує журнал (n) вставки / видалення / пошуку (та містять елементи у відсортованому порядку). Це все, для чого використовується карта, і реалізується (як правило) у вигляді червоного / чорного дерева. Червоне / чорне дерево гарантує збалансованість дерева. Тож глибина дерева безпосередньо пов’язана з кількістю елементів у дереві.
Мартін Йорк

14
Я не згоден з цією відповіддю як у 2008 році, так і зараз. Стандартна бібліотека не має "прискореного" поштовху, і наявність чогось прискореного прискорення не повинна бути (і не була) причиною не приймати її у стандарт. Крім того, BGL є загальним і достатньо задіяним, щоб заслужити незалежні від нього спеціалізовані класи дерев. Крім того, факт, що std :: map та std :: set вимагає дерева - це ІМО, ще один аргумент для наявності stl::red_black_treeтощо. Нарешті, std::mapі std::setдерева врівноважені, std::treeможливо, не буде.
einpoklum

1
@einpoklum: "наявність чогось прискореного прискорення не повинна бути причиною того, щоб не прийняти це до стандарту" - враховуючи, що одна з цілей прискорення - це виступати довідковою базою для корисних бібліотек до включення в стандарт, я можу лише скажіть «абсолютно!».
Мартін Боннер підтримує Моніку

94

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

Деякі питання, які слід врахувати:

  • Чи число дітей для вузла є фіксованим чи змінним?
  • Скільки накладних витрат на вузол? - тобто, чи потрібні вам батьківські вказівники, покажчики братів та сестер тощо.
  • Які алгоритми надати? - різні ітератори, алгоритми пошуку тощо.

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

Ось декілька загальних реалізацій дерева:


5
"... немає хорошого способу задовольнити всіх ..." За винятком того, що оскільки stl :: map, stl :: multimap і stl :: set засновані на rb_tree stl, воно повинно задовольняти стільки ж випадків, скільки й ці основні типи .
Кацкуль

44
Зважаючи на те, що немає способу витягнути дітей з вузла std::map, я б не називав ці контейнери з дерева. Це асоціативні контейнери, які зазвичай реалізуються як дерева. Велика різниця.
Mooing Duck

2
Я погоджуюся з Mooing Duck, як би ви реалізували перший пошук на std :: map? Це буде страшенно дорого
Марко А.

1
Я почав використовувати дерево дерева.hh Kasper Peeters, однак після перегляду ліцензії на GPLv3 або будь-яку іншу версію GPL це забруднило наше комерційне програмне забезпечення. Я рекомендував би переглянути скарбник, поданий у коментарі @hplbsh, якщо вам потрібна структура для комерційних цілей.
Jake88

3
Спеціальні вимоги до сорту дерев є аргументом того, що дерева мають різні типи, а не такі взагалі.
Андре

50

Філософія STL полягає в тому, що ви вибираєте контейнер на основі гарантій, а не на основі того, як контейнер реалізований. Наприклад, ваш вибір контейнера може ґрунтуватися на потребі у швидких пошуку. У всьому, що вам важливо, контейнер може бути реалізований як односпрямований список - доки пошук буде дуже швидким, ви будете раді. Це тому, що ви жодним чином не торкаєтесь внутрішніх, ви використовуєте ітератори або функції членів для доступу. Ваш код не пов'язаний з тим, як реалізується контейнер, а з тим, наскільки він швидкий, чи має фіксований і визначений порядок, чи ефективний у просторі тощо.


12
Я не думаю, що він говорить про реалізацію контейнерів, він говорить про власне контейнер з дерева.
Mooing Duck

3
@MooingDuck Я думаю, що wilhelmtell означає, що стандартна бібліотека C ++ не визначає контейнери на основі їх основної структури даних; він визначає контейнери лише за їх інтерфейсом та спостережуваними характеристиками, такими як асимптотична продуктивність. Коли ви думаєте про це, дерево насправді не є контейнером (як ми їх знаємо). Вони навіть не мають аа прямий end()і begin()з допомогою якого ви можете перебрати всі елементи і т.д.
Jordan Melo

7
@JordanMelo: Дурниці по всіх пунктах. Це річ, яка містить предмети. Дуже тривіально розробити це, щоб мати початок () і кінець () та двонаправлені ітератори, з якими можна повторити. Кожен контейнер має різні характеристики. Було б корисно, якби можна було додатково мати характеристики дерева. Має бути досить легко.
Mooing Duck

Таким чином, хочеться мати контейнер, який забезпечує швидкий пошук дочірніх та батьківських вузлів та розумні вимоги до пам'яті.
doc

@JordanMelo: З цього погляду також адаптери, такі як черги, стеки або черги з пріоритетом, не належать до STL (їх також немає begin()і немає end()). І пам’ятайте, що черговістю пріоритетів зазвичай є купа, яка, принаймні, теоретично є деревом (навіть незважаючи на реальні реалізації). Тож навіть якщо ви реалізували дерево як адаптер, використовуючи якусь іншу базову структуру даних, воно може бути включеним до STL.
andreee

48

"Я хочу зберігати ієрархію об'єктів як дерево"

C ++ 11 з'явився і пішов, і вони все ще не бачили потреби в наданні даних std::tree, хоча ідея все ж виникла (дивіться тут ). Можливо, причиною того, що вони не додали цього, є те, що побудувати свій власний верх над існуючими контейнерами можна. Наприклад...

template< typename T >
struct tree_node
   {
   T t;
   std::vector<tree_node> children;
   };

Простий обхід використовує рекурсію ...

template< typename T >
void tree_node<T>::walk_depth_first() const
   {
   cout<<t;
   for ( auto & n: children ) n.walk_depth_first();
   }

Якщо ви хочете підтримувати ієрархію і хочете, щоб вона працювала з алгоритмами STL , тоді все може ускладнитися. Ви можете створити власні ітератори та досягти певної сумісності, проте багато алгоритмів просто не мають сенсу для ієрархії (наприклад, будь-що, що змінює порядок діапазону). Навіть визначення діапазону в межах ієрархії може бути безладною справою.


2
Якщо проект може дозволити сортування дітей дерева_ноду, то використання std :: set <> замість std :: vector <> та додавання оператора <() до об'єкта tree_node значно покращить Виконання 'пошуку' об'єкта, подібного до Т '.
J Йоргенсон

4
Виявляється, вони були ледачими і насправді зробили ваш перший приклад Невизначена поведінка.
користувач541686

2
@Mehrdad: Я нарешті вирішив попросити деталі, що стоять за вашим коментарем тут .
nobar

many of the algorithms simply don't make any sense for a hierarchy. Питання тлумачення. Уявіть структуру користувачів stackoverflow, і кожен рік ви хочете, щоб ті, хто має більшу кількість репутаційних балів, очолювали тих, хто має меншу кількість репутації. Таким чином, забезпечуючи ітератор BFS та відповідне порівняння, щороку ви просто працюєте std::sort(tree.begin(), tree.end()).
doc

До того ж, ви можете легко побудувати асоціативний дерево (для моделювання неструктурованих записів ключ-значення, як JSON, наприклад), замінивши vectorз mapв наведеному вище прикладі. Для повної підтримки структури, схожої на JSON, ви можете використовувати variantдля визначення вузлів.
nobar

43

Якщо ви шукаєте реалізацію дерева дерев RB, то stl_tree.h може підійти і для вас.


14
Як не дивно, це єдина відповідь, яка насправді відповідає на початкове запитання.
Кацкуль

12
Враховуючи, що він хоче "Єпархії", здається безпечним припустити, що що-небудь із "врівноваженням" - це неправильна відповідь.
Mooing Duck

11
"Це внутрішній файл заголовка, включений до інших заголовків бібліотеки. Не слід намагатися використовувати його безпосередньо."
Ден

3
@Dan: Копіювання не означає використання його безпосередньо.
einpoklum

12

карта std :: заснована на червоному чорному дереві . Ви також можете використовувати інші контейнери, щоб допомогти вам реалізувати власні типи дерев.


13
Зазвичай він використовує червоно-чорні дерева (цього робити не потрібно).
Мартін Йорк

1
GCC використовує дерево для реалізації карти. Хтось хоче переглянути свій VC-каталог, щоб побачити, що використовує Microsoft?
JJ

// Червоно-чорне дерево класу, призначене для використання при реалізації STL // асоціативних контейнерів (набір, мультисети, карта та мультимапа). Схопив це з мого файлу stl_tree.h.
JJ

@JJ Принаймні в Studio 2010 він використовує внутрішній ordered red-black tree of {key, mapped} values, unique keysклас, визначений в <xtree>. Наразі не маєте доступу до більш сучасної версії.
Час Джастіна - Поновіть Моніку

8

Зрештою, std :: map - це дерево (воно повинно мати ті ж характеристики продуктивності, що і врівноважене бінарне дерево), але воно не відкриває інших функціональних можливостей дерева. Вірогідне обґрунтування того, що не включати реальну структуру даних про дерево, ймовірно, було лише питанням про те, щоб не включати все у stl. СТЛ можна розглядати як основу для використання у реалізації власних алгоритмів та структур даних.

Загалом, якщо є основна функціональність бібліотеки, яку ви хочете, це не в stl, виправлення полягає в тому, щоб подивитися на BOOST .

В іншому випадку, є зв'язка з бібліотек з там , в залежності від потреб вашого дерева.


6

Всі контейнери STL зовні представлені у вигляді "послідовностей" з одним ітераційним механізмом. Дерева не слідують цій ідіомі.


7
Структура даних дерева може забезпечувати проходження попереднього замовлення, впорядкування чи після впорядкування через ітератори. Насправді це робить std :: map.
Андрій Томазос

3
Так і ні ... це залежить від того, що ви маєте на увазі під «деревом». std::mapвнутрішньо реалізовується як btree, але зовні воно виглядає як упорядкована ПОСЛІДНІСТЬ ПАРІ. З огляду на будь-який елемент, ви можете загально запитати, хто є раніше, а хто після. Загальна структура дерев, що містить елементи, кожен з яких містить інші, не нав'язує сортування чи напрямку. Ви можете визначити ітераторів, які ходять по деревній структурі різними способами (перше | глибоке перше | останнє ...), але як тільки ви це зробили, std::treeконтейнер повинен повернути один з них із beginфункції. І немає очевидних причин повернути те чи інше.
Еміліо Гаравалья

4
Карта std :: зазвичай представлена ​​врівноваженим двійковим деревом пошуку, а не B-деревом. Той самий аргумент, який ви зробили, може застосовуватися до std :: unordered_set, він не має природного порядку, але подані ітератори починаються та закінчуються. Вимога початку і кінця полягає лише в тому, що вона повторює всі елементи в якомусь детермінованому порядку, а не в тому, що має бути природний. preorder - це абсолютно дійсне замовлення на ітерацію для дерева.
Андрій Томазос

4
Наслідком вашої відповіді є те, що немає nl-структури даних stl, оскільки вона не має інтерфейсу "послідовності". Це просто неправильно.
Андрій Томазос

3
@EmiloGaravaglia: Як свідчить std::unordered_set, що не існує "унікального способу" ітерації своїх членів (насправді порядок ітерації є псевдовипадковим і визначено реалізацією), але все-таки є контейнером stl - це спростує вашу думку. Ітерація над кожним елементом контейнера все ще є корисною операцією, навіть якщо порядок не визначено.
Андрій Томазос

4

Тому що STL - це не все "бібліотека". Він, по суті, містить мінімальні структури, необхідні для побудови речей.


13
Бінарні дерева є надзвичайно базовим функціоналом, а насправді більш базовим, ніж інші контейнери, оскільки такі типи, як std :: map, std :: multimap та stl :: set. Оскільки ці типи засновані на них, ви очікуєте, що базовий тип буде викритий.
Кацкуль

2
Я не думаю, що ОП запитує бінарне дерево, він просить дерево зберігати ієрархію.
Mooing Duck

Мало того, що додавання дерева "контейнера" ​​до STL означало б додати багато багатьох нових понять, наприклад, дерево-навігатор (узагальнюючи Iterator).
alfC

5
"Мінімальні структури для побудови речей" - дуже суб'єктивне твердження. Ви можете створювати речі за допомогою сировинних концепцій C ++, тому, мабуть, справжній мінімум взагалі не буде STL.
doc


3

ІМО, упущення. Але я думаю, що є вагомі причини не включати структуру дерева в STL. Існує багато логіки в підтримці дерева, яке найкраще записати як функції члена в базовий TreeNodeоб'єкт . Коли TreeNodeвін загорнувся в заголовк STL, він просто стає зручнішим.

Наприклад:

template <typename T>
struct TreeNode
{
  T* DATA ; // data of type T to be stored at this TreeNode

  vector< TreeNode<T>* > children ;

  // insertion logic for if an insert is asked of me.
  // may append to children, or may pass off to one of the child nodes
  void insert( T* newData ) ;

} ;

template <typename T>
struct Tree
{
  TreeNode<T>* root;

  // TREE LEVEL functions
  void clear() { delete root ; root=0; }

  void insert( T* data ) { if(root)root->insert(data); } 
} ;

7
У вас є багато власних сировинних покажчиків, багато з яких взагалі не мають потреби бути покажчиками.
Mooing Duck

Запропонуйте зняти цю відповідь. Клас TreeNode є частиною реалізації дерева.
einpoklum

3

Я думаю, що є кілька причин, чому немає дерев STL. В основному Дерева - це форма рекурсивної структури даних, яка, як і контейнер (список, вектор, набір), має дуже різну тонку структуру, що робить правильний вибір складним. Їх також дуже легко сконструювати в базовій формі за допомогою STL.

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

template<class A>
struct unordered_tree : std::set<unordered_tree>, A
{};

template<class A>
struct b_tree : std::vector<b_tree>, A
{};

template<class A>
struct planar_tree : std::list<planar_tree>, A
{};

Треба трохи подумати над дизайном ітератора і т. Д., Які операції з продуктом та спільним продуктом дозволяють визначити та бути ефективними між деревами - і оригінальний STL повинен бути добре записаний - так, щоб порожній набір, вектор або контейнер списку були дійсно порожній від будь-якої корисної навантаження у випадку за замовчуванням.

Дерева відіграють найважливішу роль у багатьох математичних структурах (див. Класичні праці Бутчера, Гроссмана та Ларсена; також документи Коннеса та Крімера для прикладів їх приєднання та способів їх перерахування). Неправильно думати, що їхня роль - це просто полегшити певні інші операції. Швидше вони полегшують виконання цих завдань через їх основну роль як структури даних.

Однак, окрім дерев, є ще й «ко-дерева»; дерева насамперед мають властивість, що якщо ви видалите корінь, ви видалите все.

Розглянемо ітератори на дереві, ймовірно, вони були б реалізовані як простий стек ітераторів, до вузла та до його батьківського, ... аж до кореня.

template<class TREE>
struct node_iterator : std::stack<TREE::iterator>{
operator*() {return *back();}
...};

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

Дерева неймовірно корисні, вони мають багато структури, це робить серйозним завданням отримати остаточно правильний підхід. На мою думку, саме тому вони не реалізовані в STL. Більше того, в минулому я бачив, як люди стають релігійними і знаходять уявлення про тип контейнера, що містить екземпляри свого типу, що викликає виклик - але вони повинні зіткнутися з цим - ось що представляє тип дерева - це вузол, що містить можливо порожня колекція (менших) дерев. Поточна мова дозволяє це без проблем, якщо конструктор за замовчуванням container<B>не виділяє місця в купі (або деінде) для і Bт.д.

Я, наприклад, був би радий, якби це в хорошій формі знайшло свій шлях до стандарту.


0

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

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

Моя ідея полягала в тому, що дерево повинно базуватися на існуючих контейнерах STL і не повинно приховувати контейнер, щоб воно було доступним для використання з алгоритмами STL.

Інша важлива особливість, яку має надати дерево, - це ітератори переходу.

Ось що мені вдалося придумати: https://github.com/igagis/utki/blob/master/src/utki/tree.hpp

А ось тести: https://github.com/igagis/utki/blob/master/tests/tree/tests.cpp


-9

Всі контейнери STL можна використовувати з ітераторами. Ви не можете мати ітератор дерева, тому що у вас немає одного правильного способу пройти через дерево.


3
Але ви можете сказати, що BFS або DFS - це правильний шлях. Або підтримати їх обох. Або будь-який інший, який ви можете собі уявити. Скажіть користувачеві, що це таке.
tomas789

2
у std :: map є ітератор дерева.
Джай

1
Дерево може визначити свій власний тип ітератора, який проходить усі вузли в порядку від одного "крайнього" до іншого (тобто для будь-якого бінарного дерева з контурами 0 і 1 він може запропонувати ітератор, який переходить від "всіх 0" до "всіх 1s «і зворотний ітератор , який робить зворотний; для дерева з глибиною 3 і вихідним вузлом s, наприклад, він може перебирати вузли , як s000, s00, s001, s0, s010, s01, s011, s, s100, s10, s101, s1, s110, s11, s111(» крайній лівий »на" крайній правий "), він також може використовувати шаблон глибини обходу ( s, s0, s1, s00, s01, s10,s11 ,
Час Джастіна - Відновіть Моніку

і т. д.) або якийсь інший зразок, якщо він перебирається над кожним вузлом таким чином, що кожен проходить лише один раз.
Час Джастіна - Відновіть Моніку

1
@doc, дуже хороший момент. Я думаю, що std::unordered_setбула "зроблена" послідовність, тому що ми не знаємо кращого способу ітерації над елементами, ніж якийсь довільний спосіб (внутрішньо заданий хеш-функцією). Я думаю, що це дерево навпаки: ітерація unordered_setне визначена, теоретично не існує способу визначення ітерації, окрім, можливо, "випадковим чином". У випадку з деревом існує багато "хороших" (невипадкових) способів. Але, знову ж таки, ваша думка справедлива.
alfC
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.