为什么C ++ STL不提供任何“树”容器?


373

为什么C ++ STL不提供任何“树”容器,而最好使用什么呢?

我想将对象的层次结构存储为树,而不是将树用作性能增强...


7
我需要一棵树来存储层次结构的表示形式。
罗迪

20
我和那个拒绝“正确”答案的人在一起,这似乎是正确的。“树无用”。如果树木用途晦涩,那很重要。
Joe Soul-bringer 09年

我认为原因很简单-尚无人在标准库中实现它。就像标准库没有std::unordered_mapstd::unordered_set直到最近。在此之前,标准库中根本没有STL容器。
doc

1
我的想法(尽管从未读过相关标准,因此这是一个评论而不是答案)是STL不在乎特定的数据结构,它在乎有关复杂性的规范以及所支持的操作。因此,只要满足规范,所使用的基础结构可能在实现和/或目标体系结构之间有所不同。我很确定,std::map并且std::set会在其中的每个实现中使用树,但是如果某些非树结构也符合规范,则它们不必这样做。
Mark K Cowan

Answers:


182

您可能要使用树有两个原因:

您想使用树状结构来反映问题:
为此,我们有增强图形库

或者您想要一个具有树状访问特征的容器,为此,我们有

基本上,这两个容器的特性是它们实际上必须使用树来实现(尽管实际上这不是必需的)。

另请参见以下问题: C树实现


64
使用树有很多很多原因,即使最常见。最常见!等于全部。
Joe Soul-bringer 09年

3
想要树的第三个主要原因是要对具有快速插入/删除操作的始终排序的列表进行操作,但是为此需要std:multiset。
VoidStar '02

1
@Durga:不确定将地图用作排序容器时深度的相关性。映射保证log(n)插入/删除/查找(并按排序顺序包含元素)。这是所有地图都用于(通常)作为红/黑树使用的地图。红色/黑色的树确保树是平衡的。因此,树的深度与树中元素的数量直接相关。
马丁·约克

14
我不同意这个答案,无论是在2008年还是现在。标准库没有“具有”增强功能,并且增强功能中某些内容的可用性不应(也一直是)不将其纳入标准的原因。另外,BGL是通用的,并且涉及到足以值得与之无关的专用树类。另外,IMO认为std :: map和std :: set需要一棵树的事实是另一个具有stl::red_black_treeetc的参数。最后,std::mapand std::set树是平衡的,std::tree可能不是。
einpoklum '16

1
@einpoklum:“ boost的可用性不应该成为不将其纳入标准的原因”-鉴于boost 的目的之一是在纳入标准之前充当有用库的试验场,我只能说“绝对!”。
马丁·邦纳

94

可能出于同样的原因,boost中没有树容器。有很多方法可以实现这样的容器,并且没有令人满意的方法来满足使用它的每个人。

要考虑的一些问题:

  • 节点的子代数是固定的还是可变的?
  • 每个节点多少开销?-例如,您是否需要父指针,兄弟指针等。
  • 提供什么算法?-不同的迭代器,搜索算法等

最终,问题最终出在一个对所有人都足够有用的树形容器,它的重量太大,无法满足大多数使用它的人的需求。如果您正在寻找功能强大的东西,Boost Graph Library本质上是树库可用于什么的超集。

这是其他一些通用树实现:


5
“ ...没有令人满意的方法让所有人满意...”除了stl :: map,stl :: multimap和stl :: set基于stl的rb_tree之外,它应该满足的基本条件与基本类型一样多。
Catskul

44
考虑到没有办法检索a的节点的子代std::map,因此我不称这些树容器。这些是通常实现为树的关联容器。很大的区别。
Mooing Duck 2013年

2
我同意Mooing Duck,您将如何在std :: map上进行广度优先搜索?这将非常昂贵
Marco A.

1
我开始使用Kasper Peeters的tree.hh,但是在查看了GPLv3或任何其他GPL版本的许可后,它将污染我们的商业软件。如果您需要用于商业目的的结构,我建议您查看@hplbsh注释中提供的treetree。
Jake88

3
对树木的特定品种要求是拥有不同类型的树木而不是根本没有树木的论点。
安德烈

50

STL的理念是,您根据保证而不是根据容器的实现方式来选择容器。例如,您对容器的选择可能基于对快速查找的需求。尽您所能,容器可以实现为单向列表-只要搜索非常快,您就会感到满意。那是因为您无论如何都没有接触内部,而是使用迭代器或成员函数进行访问。您的代码并不局限于容器的实现方式,而是取决于它的运行速度,它是否具有固定的和已定义的顺序,或者在空间上是否有效等等。


12
我认为他不是在谈论容器实现,而是在谈论实际的树容器本身。
Mooing Duck 2013年

3
@MooingDuck我认为wilhelmtell的意思是C ++标准库没有根据容器的底层数据结构来定义容器。它仅通过容器的界面和可观察到的特征(如渐近性能)来定义容器。当您考虑它时,一棵树根本不是一个容器(据我们所知)。他们甚至没有AA直线前进end(),并begin()与您可以通过所有元素等迭代
乔丹梅洛

7
@JordanMelo:在所有方面都是胡扯。这是一个包含对象的东西。将其设计为具有begin()和end()以及双向迭代器来进行迭代非常简单。每个容器具有不同的特征。如果一个人还可以具有树的特征,那将很有用。应该很容易。
Mooing Duck 2015年

因此,人们希望拥有一种能够为子节点和父节点提供快速查找以及合理的内存需求的容器。
doc

@JordanMelo:从这个角度来看,诸如队列,堆栈或优先级队列之类的适配器也不属于STL(它们也不具有begin()and 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
如果项目可以对tree_node的子级进行排序,则使用std :: set <>代替std :: vector <>,然后向tree_node对象添加operator <()将会大大改善类“ T”对象的“搜索”性能。
J Jorgenson

4
事实证明,它们很懒,实际上使您成为第一个示例“未定义行为”。
user541686 2014年

2
@Mehrdad:我终于决定去向的评论背后的细节在这里
nobar 2015年

many of the algorithms simply don't make any sense for a hierarchy。一个解释的问题。想象一下堆栈溢出用户的结构,每年您都希望信誉得分高的用户领导信誉得分低的用户。这样,您只需每年运行一次,即可提供BFS迭代器和适当的比较std::sort(tree.begin(), tree.end())
doc

同样的道理,你可以很容易地建立一个联合树,用(非结构化键值记录模型,如JSON为例)vectormap上面的例子。为了完全支持类似JSON的结构,可以使用variant定义节点。
nobar

43

如果您正在寻找RB树实现,那么stl_tree.h也可能适合您。


14
奇怪的是,这是实际回答原始问题的唯一答案。
Catskul,

12
考虑到他想要一个“等级制”,似乎可以肯定地说,任何带有“平衡”的东西都是错误的答案。
Mooing Duck 2013年

11
“这是一个内部头文件,包含在其他库头文件中。您不应尝试直接使用它。”

3
@丹:复制它并不意味着直接使用它。
einpoklum 2016年

12

std :: map基于一棵红黑树。您还可以使用其他容器来帮助您实现自己的树类型。


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可以看作是用于实现自己的算法和数据结构的框架。

通常,如果您需要基本的库功能,而stl中没有此功能,则解决方法是查看BOOST

否则,有一个一堆 那里,这取决于你的树的需求。


6

所有STL容器在外部都通过一种迭代机制表示为“序列”。树木没有遵循这种习语。


7
树数据结构可以通过迭代器提供预顺序,有序或后顺序遍历。实际上,这就是std :: map的作用。
Andrew Tomazos 2012年

3
是和否……这取决于您所说的“树”。std::map在内部实现为btree,但在外部则显示为PAIRS的排序SEQUENCE。给定任何要素,您可以普遍询问谁在谁之后。包含元素(其中每个元素都包含其他元素)的常规树结构不施加任何排序或方向。您可以定义以多种方式遍历树结构的迭代器(sallow | deepfirst | last ... last),但是一旦完成,std::tree容器必须从begin函数中返回其中的一个。而且没有明显的理由退还一个或另一个。
Emilio Garavaglia 2012年

4
std :: map通常由平衡的二叉搜索树而不是B树表示。您所做的相同参数可以应用于std :: unordered_set,它没有自然顺序,但是可以显示开始和结束迭代器。开始和结束的要求仅仅是按照确定的顺序迭代所有元素,而不是必须有自然的顺序。preorder是树的完全有效的迭代顺序。
安德鲁·托马佐斯

4
您的答案的含义是没有stl n-tree数据结构,因为它没有“序列”接口。这根本是不正确的。
安德鲁·托马佐斯

3
@EmiloGaravaglia:如所证明的那样std::unordered_set,它没有“唯一的方式”来迭代其成员(实际上,迭代顺序是伪随机的,并且定义了实现),但仍然是stl容器-这证明了您的观点。即使未定义顺序,遍历容器中的每个元素仍然是有用的操作。
Andrew Tomazos 2012年

4

因为STL并不是“一切”库。它实质上包含构建事物所需的最小结构。


13
二进制树是一种极其基本的功能,并且实际上,由于诸如std :: map,std :: multimap和stl :: set之类的类型,二叉树比其他容器更基本。由于这些类型基于它们,因此您希望公开基础类型。
卡特斯克尔

2
我不认为OP要求一个二叉树,他要求一个树来存储层次结构。
Mooing Duck 2013年

不仅如此,在STL中添加树“容器”还意味着要添加许多新概念,例如,树导航器(广义化Iterator)。
alfC

5
“建造事物的最小结构”是一个非常主观的说法。您可以使用原始的C ++概念构建事物,因此我认为真正的最低要求是根本没有STL。
doc


3

海事组织,一个遗漏。但是我认为有充分的理由不在STL中包含Tree结构。维护树有很多逻辑,最好将树作为成员函数写入基础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 2013年

建议您撤回此答案。TreeNode类是树实现的一部分。
einpoklum 2016年

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必须写得很好-以便空集,向量或列表容器是在默认情况下,实际上没有任何有效负载。

树木在许多数学结构中都起着至关重要的作用(请参阅Butcher,Grossman和Larsen的经典论文;有关如何加入树木以及如何使用它们进行枚举的信息,请参阅Connes和Kriemer的论文)。认为他们的作用仅仅是促进某些其他行动是不正确的。相反,由于它们作为数据结构的基本作用,它们使这些任务变得容易。

但是,除了树木外,还有“共同树”。所有这些树都具有以下属性:如果删除根,则删除所有内容。

考虑一下树上的迭代器,可能将它们实现为一个简单的迭代器堆栈,从一个节点到其父节点,直到根。

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中有树迭代器。
2015年

1
一棵树可以定义自己的自定义迭代器类型,该遍历器从一个“极端”到另一个节点遍历所有节点(即,对于路径0和1的任何二叉树,它可以提供从“全0”到“全”的迭代器) 1“并且,做了相反的反向迭代;对于具有3的深度和起始节点的树s,例如,它可以遍历节点作为s000s00s001s0s010s01s011ss100s10s101s1s110s11s111(”最左边的”到‘最右’);它也可以使用深度遍历模式(ss0s1s00s01s10s11
贾斯汀时间-恢复莫妮卡

等等),或者其他某种模式,只要它以使每个节点仅传递一次的方式遍历每个节点即可。
贾斯汀时间-恢复莫妮卡

1
@doc,非常好。我认为这std::unordered_set是“序列化”的序列,因为除了某种任意方式(由散列函数内部提供)之外,我们不知道一种更好的迭代元素的方式。我认为这是树的相反情况:迭代unordered_set未充分指定,从理论上讲,除了“随机”之外,没有其他方法可以定义迭代。就树而言,有许多“好”(非随机)方式。但是,您的观点再次是正确的。
alfC
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.