为什么C ++ STL不提供任何“树”容器,而最好使用什么呢?
我想将对象的层次结构存储为树,而不是将树用作性能增强...
std::unordered_map
,std::unordered_set
直到最近。在此之前,标准库中根本没有STL容器。
std::map
并且std::set
会在其中的每个实现中使用树,但是如果某些非树结构也符合规范,则它们不必这样做。
为什么C ++ STL不提供任何“树”容器,而最好使用什么呢?
我想将对象的层次结构存储为树,而不是将树用作性能增强...
std::unordered_map
,std::unordered_set
直到最近。在此之前,标准库中根本没有STL容器。
std::map
并且std::set
会在其中的每个实现中使用树,但是如果某些非树结构也符合规范,则它们不必这样做。
Answers:
您可能要使用树有两个原因:
您想使用树状结构来反映问题:
为此,我们有增强图形库
或者您想要一个具有树状访问特征的容器,为此,我们有
基本上,这两个容器的特性是它们实际上必须使用树来实现(尽管实际上这不是必需的)。
另请参见以下问题: C树实现
stl::red_black_tree
etc的参数。最后,std::map
and std::set
树是平衡的,std::tree
可能不是。
可能出于同样的原因,boost中没有树容器。有很多方法可以实现这样的容器,并且没有令人满意的方法来满足使用它的每个人。
要考虑的一些问题:
最终,问题最终出在一个对所有人都足够有用的树形容器,它的重量太大,无法满足大多数使用它的人的需求。如果您正在寻找功能强大的东西,Boost Graph Library本质上是树库可用于什么的超集。
这是其他一些通用树实现:
std::map
,因此我不称这些树容器。这些是通常实现为树的关联容器。很大的区别。
STL的理念是,您根据保证而不是根据容器的实现方式来选择容器。例如,您对容器的选择可能基于对快速查找的需求。尽您所能,容器可以实现为单向列表-只要搜索非常快,您就会感到满意。那是因为您无论如何都没有接触内部,而是使用迭代器或成员函数进行访问。您的代码并不局限于容器的实现方式,而是取决于它的运行速度,它是否具有固定的和已定义的顺序,或者在空间上是否有效等等。
end()
,并begin()
与您可以通过所有元素等迭代
begin()
and end()
)。请记住,优先级队列通常是一个堆,至少在理论上是一棵树(即使是实际的实现)。因此,即使您使用某些不同的基础数据结构将树实现为适配器,也可以将其包含在STL中。
“我想将对象的层次结构存储为树”
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算法一起使用,那么事情可能会变得复杂。您可以构建自己的迭代器并实现某种兼容性,但是许多算法对层次结构根本没有任何意义(例如,任何更改范围顺序的事物)。即使在层次结构中定义范围也可能是一件麻烦的事。
many of the algorithms simply don't make any sense for a hierarchy
。一个解释的问题。想象一下堆栈溢出用户的结构,每年您都希望信誉得分高的用户领导信誉得分低的用户。这样,您只需每年运行一次,即可提供BFS迭代器和适当的比较std::sort(tree.begin(), tree.end())
。
如果您正在寻找RB树实现,那么stl_tree.h也可能适合您。
ordered red-black tree of {key, mapped} values, unique keys
定义的内部类<xtree>
。目前无法访问更新版本。
所有STL容器在外部都通过一种迭代机制表示为“序列”。树木没有遵循这种习语。
std::map
在内部实现为btree,但在外部则显示为PAIRS的排序SEQUENCE。给定任何要素,您可以普遍询问谁在谁之后。包含元素(其中每个元素都包含其他元素)的常规树结构不施加任何排序或方向。您可以定义以多种方式遍历树结构的迭代器(sallow | deepfirst | last ... last),但是一旦完成,std::tree
容器必须从begin
函数中返回其中的一个。而且没有明显的理由退还一个或另一个。
std::unordered_set
,它没有“唯一的方式”来迭代其成员(实际上,迭代顺序是伪随机的,并且定义了实现),但仍然是stl容器-这证明了您的观点。即使未定义顺序,遍历容器中的每个元素仍然是有用的操作。
因为STL并不是“一切”库。它实质上包含构建事物所需的最小结构。
这个看起来很有前途,似乎正是您想要的:http : //tree.phi-sci.com/
海事组织,一个遗漏。但是我认为有充分的理由不在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); }
} ;
我认为没有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
,等在堆(或其他任何地方)上分配空间。
如果能以一种好的形式将其纳入标准,我将感到高兴。
在这里仔细阅读答案,常见的命名原因是:一个人不能遍历该树,或者该树不具有与其他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
所有STL容器均可与迭代器一起使用。您不能在树上设置迭代器,因为没有“一种正确的”方法可以遍历树。
s
,例如,它可以遍历节点作为s000
,s00
,s001
,s0
,s010
,s01
,s011
,s
,s100
,s10
,s101
,s1
,s110
,s11
,s111
(”最左边的”到‘最右’);它也可以使用深度遍历模式(s
,s0
,s1
,s00
,s01
,s10
,s11
,
std::unordered_set
是“序列化”的序列,因为除了某种任意方式(由散列函数内部提供)之外,我们不知道一种更好的迭代元素的方式。我认为这是树的相反情况:迭代unordered_set
未充分指定,从理论上讲,除了“随机”之外,没有其他方法可以定义迭代。就树而言,有许多“好”(非随机)方式。但是,您的观点再次是正确的。