为什么C++ STL不提供任何"树"容器,而最好使用什么呢?
我希望将对象的层次结构存储为树,而不是使用树作为性能增强…
- 你需要一棵树做什么?
- 我需要一棵树来存储层次结构的表示。
- 我和那个投了"正确"答案的人在一起,这个答案似乎是"树是无用的"。如果树木的用途不明确,那就很重要。
- 我认为原因是微不足道的——还没有人在标准库中实现它。就像标准库直到最近才有std::unordered_map和std::unordered_set。在此之前,标准库中根本没有STL容器。
- 我的想法(虽然从未阅读过相关标准,因此这是一个评论而不是答案)是STL不关心具体的数据结构,它关心关于复杂性和支持哪些操作的规范。因此,所使用的底层结构可能在实现和/或目标架构之间有所不同,前提是它满足规范。我很肯定std::map和std::set会在所有实现中使用树,但是如果一些非树结构也符合规范,它们就不必使用树。
使用树有两个原因:
您希望使用类似树的结构来镜像问题:为此,我们有Boost图形库
或者需要具有树状访问特性的容器为此我们有
- std::map(和std::multimap)
- std::set(和std::multiset)
基本上,这两个容器的特点是,它们实际上必须使用树来实现(尽管这实际上不是一个要求)。
另请参见此问题:C树实现
- 使用一棵树有很多很多原因,即使这些是最常见的。最常见的!全部相等。
- 想要一棵树的第三个主要原因是,它的列表总是经过排序的,并且插入/删除速度很快,但是有std:multiset。
- 请STD::多组和STD::多点列表。
- 当我们不知道树的深度时,如何使用映射作为树类数据结构??
- @Durga:当你把map作为一个排序的容器时,不确定深度是如何相关的。map保证日志(n)的插入/删除/查找(并按排序顺序包含元素)。这是所有的地图都被用于并实现(通常)为红/黑树。红色/黑色的树确保树是平衡的。因此,树的深度与树中元素的数量直接相关。
- 无论是在2008年还是现在,我都不同意这个答案。标准库没有"增强"功能,增强功能中的某些内容的可用性不应该(也不是)成为不将其应用到标准中的原因。此外,BGL是通用的,涉及到足够多的优点,专门树类独立于它。此外,事实上,STD:STMA:STD::SET需要一个树,IMO,另一个参数有EDCOX1,2,等等。最后,EDOCX1,3,EDCX1,4的树是平衡的,EDCOX1×5可能不是。
- @Einpoklum:"Boost中的某些内容的可用性不应成为不将其纳入标准的理由"——鉴于Boost的目的之一是在纳入标准之前作为有用图书馆的试验场,我只能说"绝对!".
可能也是因为Boost中没有树容器。实现这样一个容器有很多方法,而且没有好的方法来满足每个使用它的人。
需要考虑的一些问题:-节点的子节点数是固定的还是可变的?-每个节点的开销是多少?-你需要父指针、兄弟指针等吗?-提供什么算法?-不同的迭代器、搜索算法等。
最后,问题是一个对每个人都足够有用的树容器太重了,不能满足大多数使用它的人。如果您正在寻找功能强大的东西,Boost图形库实际上是一个树库可以用来做什么的超集。
下面是一些其他通用树实现:-卡斯帕·皮特斯的树。-土坯森林-核心::树
- 和code.google.com/p/treetree(Apache2.0许可证)
- "…没有好的方法来满足所有人…",除了stl::map、stl::multimap和stl::set基于stl的rb_树之外,它应该满足与这些基本类型一样多的情况。
- 考虑到无法检索std::map节点的子节点,我不会调用这些树容器。这些是通常作为树实现的关联容器。差别很大。
- 我同意"嗅鸭",你如何在STD:图上实现广度优先搜索?会非常贵的
- 我开始使用kasper peeters'tree.hh,但是在审查了gplv3或任何其他gpl版本的许可之后,它会污染我们的商业软件。如果您出于商业目的需要结构,我建议您查看@hplbsh在评论中提供的treetree。
- 实际上,Boost提供了一个树容器。这个库称为boost.propertytree。
- 我最终使用了datasoftsolutions.net/tree_container_library/overview.php,我尝试了treetree,但是文档比较少,使用起来非常困难。propertytree可以很好地处理字符串,但没有看到使用其他任何东西的例子。TCL仅限头部,商业友好,STL友好。为了我的目的,它工作得很好。
- 对树木的品种特定要求是有不同类型的树的论点,而不是完全没有。
- 是否可以创建一个模板类或结构来充当树,允许用户将其需求指定为模板参数?我认为这是实现标准树的最一般的方法,尽管它可能有点笨拙。
- Boost图形库提供了一个R树。
STL的理念是,您选择一个基于保证的容器,而不是基于容器是如何实现的。例如,容器的选择可能基于快速查找的需要。对于您所关心的一切,容器可以实现为一个单向列表——只要搜索速度非常快,您就会很高兴。这是因为您无论如何都不会触及内部,而是使用迭代器或成员函数进行访问。您的代码与容器的实现方式无关,而是与它的速度、它是否具有固定的和定义的顺序、它在空间上是否有效等有关。
- 我认为他不是在谈论容器实现,而是在谈论实际的树容器本身。
- MooingDuck,我认为WielMeTew的意思是,C++标准库不基于它们的底层数据结构定义容器;它只通过它们的接口定义了容器,并像渐近性能那样定义了可观察的特性。当你想到它的时候,一棵树根本不是一个容器(我们知道它们)。它们甚至没有一个直接的end()和begin(),您可以用它迭代所有元素,等等。
- @乔丹梅洛:胡说八道。它是一个包含对象的东西。设计一个begin()和end()以及双向迭代器进行迭代非常简单。每个容器都有不同的特性。如果一个人可以另外拥有树的特征,这将是有用的。应该很容易。
- 因此,我们希望拥有一个容器,能够快速查找子节点和父节点,并满足合理的内存需求。
- @Jordanmelo:从这个角度来看,队列、堆栈或优先级队列等适配器也不属于STL(它们也没有begin()和end())。请记住,优先级队列通常是一个堆,至少在理论上是一个树(即使实际实现)。因此,即使您使用一些不同的底层数据结构将树实现为适配器,它也可以包含在STL中。
- @安德烈当然没有人说只有容器属于STL。容器适配器不符合Container的概念,正如您所观察到的,它们显然在stl中。你以priority_queue为例,但我认为你会同意它不会暴露任何树样行为,并且利用树结构来达到非常特殊的目的。但是普通的树呢?记住,树结构最重要的功能之一是建立关系模型,这种关系并不总是遵循二叉树结构。
"I want to store a hierarchy of objects as a tree"
号
C++ 11已经来了又走了,他们仍然没有看到需要提供一个EDCOX1×0的概念,尽管这个想法确实出现了(见这里)。也许他们没有添加这一点的原因是,在现有容器之上构建自己的容器非常简单。例如。。。
1 2 3 4 5 6
| template< typename T >
struct tree_node
{
T t;
std::vector<tree_node> children;
}; |
简单的遍历将使用递归…
1 2 3 4 5 6
| template< typename T >
void tree_node<T>::walk_depth_first() const
{
cout<<t;
for ( auto & n: children ) n.walk_depth_first();
} |
号
如果您希望维护一个层次结构,并且希望它与STL算法一起工作,那么事情可能会变得复杂。您可以构建自己的迭代器并实现一些兼容性,但是许多算法对于层次结构来说根本没有任何意义(例如,任何改变范围顺序的东西)。即使在层次结构中定义一个范围,也可能是一个混乱的业务。
- 如果STD:可以允许树节点的子节点排序,那么使用STD::SET>代替STD::vector < >然后向TeeReNoject对象添加一个运算符<(),将大大提高一个"T"类对象的"搜索"性能。
- 结果发现他们很懒惰,实际上让你的第一个例子没有定义行为。
- @梅尔达:我终于决定在这里征求你的意见背后的细节。
- many of the algorithms simply don't make any sense for a hierarchy。解释问题。想象一下StackOverflow用户的结构,每年你都希望那些拥有较高信誉点数的用户成为那些拥有较低信誉点数的用户的老板。因此,提供了bfs迭代器和适当的比较,您每年只运行std::sort(tree.begin(), tree.end())。
如果您正在寻找一个RB树实现,那么stl_tree.h也可能适合您。
- 奇怪的是,这是唯一真正回答原始问题的答案。
- 考虑到他想要一个"贵族统治",似乎可以安全地假设任何"平衡"的东西都是错误的答案。
- "这是一个内部头文件,由其他库头包含。您不应试图直接使用它。"
- @丹:复制并不意味着直接使用它。
STD::地图是基于红黑树。您还可以使用其他容器来帮助实现自己的树类型。
- 它通常使用红黑树(不需要这样做)。
- GCC使用树来实现映射。有人想看看他们的vc include目录,看看微软用了什么?
- //红黑树类,设计用于实现stl//关联容器(set、multiset、map和multimap)。从我的stl-tree.h文件中抓到的。
- @J.J.至少在2010工作室,它使用了一个内部的ordered red-black tree of {key, mapped} values, unique keys类,在中定义。现在没有更现代的版本。
在某种程度上,STD::MAP是一棵树(它需要具有与平衡二叉树相同的性能特性),但是它不会暴露其他树的功能。不包括真正的树数据结构的原因可能只是不包括STL中的所有内容。STL可以看作一个框架,用于实现您自己的算法和数据结构。
一般来说,如果您需要一个基本的库功能,而这不在STL中,那么修复方法就是查看boost。
否则,根据树的需要,会有很多库。
所有STL容器在外部用一个迭代机制表示为"序列"。树不遵循这个成语。
- 树数据结构可以通过迭代器提供预序、无序或后序遍历。事实上,这就是STD:地图的作用。
- 是和否…这取决于你所说的"树"是什么意思。std::map在内部实现为btree,但在外部则显示为成对的排序序列。考虑到任何因素,你都可以普遍地问谁在前谁在后。一个包含元素的通用树结构,每个元素都包含其他元素,不强制任何排序或方向。可以通过多种方式定义遍历树结构的迭代器(sallow deep first last…),但一旦这样做,std::tree容器必须从begin函数返回其中一个迭代器。而且没有明显的理由返回其中一个或另一个。
- STD::MAP一般用平衡二叉搜索树表示,而不是B-树。同样的论点可以应用于STD::无序的集合,它没有自然的顺序,但是呈现了开始和结束迭代器。开始和结束的要求只是以某种确定的顺序迭代所有元素,而不是必须有一个自然的元素。预订单是一个完全有效的树迭代顺序。
- "STD::MAP一般用平衡二叉搜索树来表示,而不是B-树。":你在玩什么游戏?btree是二进制搜索树的一种实现,map也不是这样"表示"的。它就是这样"实现"的。它表示为从begin()开始并在end()之前终止的已排序对序列。的确,您可以从begin()开始浏览一棵树,直到end(),但是,除非为它定义了一个目的(如map所做的),否则除了将元素放入一个列表之外,再也没有线索了。
- 您的答案意味着没有STL N-Tree数据结构,因为它没有"序列"接口。这是完全错误的。
- 不:因为它没有一种独特的方式来定义序列接口,因为它的正常用途是不只是简单的序列。将其中一个容器作为"标准"使用会破坏拥有这样一个结构的优势,而不将其使用会使该结构对无效,其他容器都符合。你可以自己做,但是你做的方式会与别人的方式相冲突,而且没有技术上的理由去选择一个或另一个。所以没有"标准"的方法。
- @Emilogaravaglia:正如std::unordered_set所证明的那样,它没有迭代其成员的"独特方式"(实际上迭代顺序是伪随机的,并且定义了实现),但仍然是一个STL容器——这否定了您的观点。循环访问容器中的每个元素仍然是一个有用的操作,即使顺序未定义。
- 如果是我无法解释或是你不想理解的话,我就再也无法忍受了。unordered_set没有独特的分类方式,但有独特的行走方式:从begin()开始,到end()结束,可以服从的任何事物。但是,一棵有着独特行走方式的树是没有用的,因为树的目的至少是沿着树桩和左右方向行走。如果你不理解我的说话方式,请阅读以下内容:stackoverflow.com/a/206101/924727,这正是我的概念,还有其他措辞。
- 容器没有理由需要单向行走,可以通过不同类型的迭代器对提供不同的迭代顺序(例如参见rbegin/rend)。std:树可以有类似于breadth_begin/breadth_end/depth_begin/depth_end的东西分别用于上下和左右顺序。你链接到的答案是正确的,是潜在结构的巨大差异导致它被排除在标准库之外,但这与"序列"或"一个迭代机制"无关。
- 必须这样做,因为它们是造成这种差异的主要原因。你可以自由地不去管它,但让我相信与上帝的和平!
- @Emiliogaravaglia我明白你的意思。您希望能够在树容器中选择迭代策略,因为a)树在语义上与您的数据相匹配b)您希望遍历特定的树来解决问题。可能的解决方案:比如说,std::tree有一个枚举标记模板参数,它告诉范围的begin()给定的默认遍历顺序。此模板arg默认为traversal::inorder。你甚至可以给其他人起名字:template using tree_pre = tree;等。这是一个可行的问题。
- 在另一个新闻中,为下一个标准,很多工作正在进入迭代器库。也许里面的东西会有帮助。
- @埃姆斯:好建议。当然,3年后,有些东西在进化,但是…实际上,它引入了一个新的概念(范围),实际上允许打破约束。
这一个看起来很有希望,似乎是你想要的:http://tree.phi-sci.com网站/
因为STL不是"一切"库。它基本上包含了建造东西所需的最小结构。
- 二进制树STD:一个非常基本的功能,事实上,它比其他容器更基本,因为像STD::MAP,STD::MultIMAP,STL::SET。因为这些类型是基于它们的,所以您希望底层类型是公开的。
- 我不认为OP要求二叉树,他要求一棵树来存储层次结构。
- 不仅如此,向STL添加树"容器"意味着添加许多新概念,例如树导航器(通用迭代器)。
- "建筑物的最小结构"是一个非常主观的说法。你可以用原始C++概念构建事物,所以我猜测真正的最小值根本就不是STL。
我认为没有STL树有几个原因。首先,树是递归数据结构的一种形式,就像容器(列表、向量、集合)一样,具有非常不同的精细结构,这使得正确的选择变得困难。它们也很容易使用STL以基本形式构造。
有限根树可以被认为是一个具有值或有效负载的容器,例如类A的一个实例和根(子)树的可能空集合;没有子树的树看起来像树叶。
1 2 3 4 5 6 7 8 9 10 11
| 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必须编写得很好——这样,在默认情况下,空的集合、向量或列表容器实际上没有任何有效负载。
树在许多数学结构中扮演着重要的角色(参见《屠夫、格罗斯曼和拉森的经典论文》;也可以结合康纳斯和克里默的论文,以获取它们的示例,以及如何使用它们进行枚举)。认为他们的作用仅仅是促进某些其他行动是不正确的。相反,由于它们作为数据结构的基本角色,它们促进了这些任务。
但是,除了树之外,还有"共树";上面的树都具有这样的属性:如果删除根,则删除所有内容。
考虑到树上的迭代器,它们可能被实现为一个简单的迭代器堆栈,一个节点,以及它的父节点,…直到根。
1 2 3 4
| template<class TREE>
struct node_iterator : std::stack<TREE::iterator>{
operator*() {return *back();}
...}; |
。
但是,您可以拥有任意多个;它们共同构成一个"树",但是当所有箭头都朝着根方向流动时,这个协树可以通过迭代器迭代到普通迭代器和根;但是它不能跨或向下导航(其他迭代器不知道),也不能取消迭代器的集合。删除,除非跟踪所有实例。
树是非常有用的,它们有很多结构,这使得获得明确正确的方法成为一个严重的挑战。在我看来,这就是它们不在STL中实现的原因。此外,在过去,我看到人们信奉宗教,并且发现一种容器的概念,其中包含了自己类型的实例,这是一种挑战——但他们必须面对它——这就是树类型所代表的——它是一个节点,其中可能包含空的(较小的)树集合。当前语言允许它不受挑战地提供container的默认构造函数,不为B等分配堆上(或其他任何地方)的空间。
我个人会很高兴,如果这是一个很好的形式,找到它的方式进入标准。
在我看来,这是一个疏忽。但我认为有充分的理由不在STL中包含树结构。维护树有很多逻辑,最好将其作为成员函数写入基TreeNode对象中。当TreeNode被一个stl头包起来时,它就变得更乱了。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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); }
} ; |
- 你有很多原始指针,其中很多根本不需要指针。
- 建议你收回这个答案。TreeNode类是树实现的一部分。
所有STL容器都可以与迭代器一起使用。你不能有一个迭代器和一个树,因为你没有"一个正确的"方法来遍历树。
- 但是你可以说BFS或DFS是正确的方法。或者支持他们两个。或者任何你能想象到的。只需告诉用户它是什么。
- 在STD::映射有树迭代器。
- 树可以定义自己的自定义迭代器类型,该类型按照从一个"极端"到另一个"极端"的顺序遍历所有节点(即,对于路径为0&1的任何二叉树,它可以提供从"0"到"1"的迭代器,以及相反的反向迭代器;对于深度为3且起始节点为s的树,例如,它可以在节点上迭代为:EDOCX11〔3〕、EDOCX11〔4〕、EDOCX11〔5〕、EDOCX11〔6〕、EDOCX11〔7〕、EDOCX11〔8〕、EDOCX11〔9〕、EDOCX11〔2〕、EDOCX11〔11〕、EDOCX11〔12〕、EDOCX11〔13〕、s1、EDOCX11〔15〕、EDOCX11〔16〕、EDOCX11〔16〕、EDOCX11〔16〕、EDOCX11〔7〕、EDOCX11〔7〕、EDOCX11〔8〕、EDOCX11〔9〕9、EDOCX11〔2〕2、EDOCX11〔11〔11〕、EDOCX11〔11〔11〕12〔12〕1〔17〕("最左边"到"最右边");它还可以使用深度遍历模式(EDOCX12、s0、s1、s00、s01、s10、s11,
- 等等),或者其他一些模式,只要它以这样一种方式迭代每个节点,每个节点只传递一次。
- 我不明白为什么这个答案被否决了。这是唯一一个真正回答这个问题的人。树不是STL接口的一部分(尽管它在内部),因为树不是STL意义上的容器,也就是说,它不是具有线性迭代的元素的容器。(例如,它没有一个独特的begin()和end())。向STL中添加树意味着添加许多新概念,例如树导航器(通用迭代器)。
- @alfc把begin()定义为根节点,把end()定义为最后一片叶子有什么问题?
- @博士,当然有很多叶子,即使你选择其中一个作为最后一个,也没有唯一的方法来线性地走这三个。
- @ALFC可以选择任何一个DFS或BFS,正如Tomas789已经指出的那样。我已经为类树结构实现了很多次迭代器。
- @博士,当然,但是横切面并不像序列中那样独特。一个普通的图可以是fs或bfs,但它仍然不能使它们成为一个序列。它们可以看作是序列,但这是不同的。
- @alfc std库不限于序列,std::unordered_map不是序列,但它已经存在于标准库中。没有"唯一迭代"或"唯一遍历"这样的东西。即使使用一个数组,您也可以以您喜欢的任何方式迭代它。迭代器准确地解决了这个问题并提供了一个抽象层,因此您可以在像std::map这样的结构上进行迭代。这都是惯例问题。
- @医生,很有道理。我认为std::unordered_set被"做成"一个序列,因为我们不知道一种更好的迭代元素的方法,而不是某种任意的方法(内部由散列函数给出)。我认为这与树的情况正好相反:在unordered_set上的迭代没有被指定,理论上除了"随机"之外,没有"任何方法"定义迭代。对于树,有许多"好"(非随机)的方法。但是,同样,你的观点是正确的。