C ++的std :: set类如何能够为任何类型的数据结构实现二叉树?

时间:2014-04-24 17:13:28

标签: c++ algorithm

我理解如何为大多数原生元素实现二叉树,例如int s或string s。所以我可以理解std::set的实现,它将具有像

这样的构造函数
switch(typeof(T)) // T being the typename/class in the implementation 
{
  case int: 
  {
      /* create a binary tree structure that uses the bitshift operator to 
         add elements, e.g. 13=1101 is created as
                                      /
                                     /
                                    /
                                   /
                                  1
                                 /
                                /
                               /
                              1
                               \
                                \
                                 0
                                /
                               1
      */
  }
  case string: 
  {
      /* Do something where the string is added to a tree by going letter-by-letter 
         and looking whether the letter is in the second half of the alphabet (?)
      */
  }
  // etcetera for every imaginable type
}

但显然这不是std::set实际实现的方式,因为即使我使用像

这样的自制数据结构它也可以创建树
struct myStruct
{
      char c; 
      bool b;
};

std::set<myStruct> mySet; 

是否可以创建一个通用的二叉树类来查看数据结构的所有位,并执行类似我上面提到的int情况的内容?

例如,在myStruct的情况下,结构的大小是2个字节的16位,因此myStruct元素S具有S.c = '!'和{{ 1}}看起来像

S.b = true

因为00010101 00000001 (c part) (b part) = \ \ 0 \ \ 0 \ \ 0 / / 1 \ [etcetera] 的ASCII值是21而且'!'作为int是1.然后,这通常是低效的,因为非常大的数据结构将对应于巨大的树可能需要更多时间来遍历然后只对元素进行基本线性搜索。

这有意义吗?我真的很困惑,如果有人在这里可以让我直截了当,我会很高兴。

4 个答案:

答案 0 :(得分:6)

What you want is a good book on templates and template meta-programming.

简而言之,std::set类只定义了一个类的原型,然后使用提供的参数(一些键类型Key,一些值类型{{)在编译类型实例化。 1}},如果没有给出Tstd::less<Key>,或者其他任何内容都会推断出来。

灵活性的很大一部分来自于能够创建部分特化以及使用其他模板和默认参数。

现在,std::allocator<std::pair<Key, T>>是为许多标准库类型和所有基本类型定义的,但不适用于自定义类型。

有三种方法可以提供比较std::less的需求:

  • 覆盖默认模板参数并将其提供给模板(如果覆盖具有状态,则向构造函数提供对象可能是有意义的。)
  • 专业化std::map
  • 添加比较运算符(std::less)。

答案 1 :(得分:4)

让我们试试你的例子:

#include <set>

struct myStruct {
    char c;
    bool b;
};

int main() {
    std::set<myStruct> mySet;
    mySet.insert(myStruct());
}

如果我们编译它,我们实际上会得到一个错误。我已将错误信息减少到有趣的部分,我们看到:

.../__functional_base:63:21: error: invalid operands to binary expression ('const myStruct' and 'const myStruct')
    {return __x < __y;}

我们可以在这里看到std::set,要做它需要做的工作,需要能够将这两个对象相互比较。让我们实现:

bool operator<(myStruct const & lhs, myStruct const & rhs) {
    if (lhs.c < rhs.c)
        return true;
    if (lhs.c > rhs.c)
        return false;
    return lhs.b < rhs.b;
}

现在代码编译正常。


所有这一切都有效,因为std::set<T>希望能够通过T比较两个std::less<T>对象,这些对象会尝试(T) lhs < (T) rhs

答案 2 :(得分:4)

这是高度特定的实现:实际实现可能会有所不同。我希望能让你了解它是如何运作的。

二叉树通常会在树中的每个点保存实际值:您的图表让我认为值仅存在于叶节点处(您是否考虑trie?)。考虑一个字符串二叉树,其中包含catduckgoosedog

   dog
  /   \
cat   duck
         \
         goose

请注意,每个节点都是集合中存在的值。 (这里,我们的集合有4个元素。)虽然也许你可以做一些0/1前缀,你需要能够将对象转换为位串(查看原始底层字节不保证工作),并不是真的需要。

您需要了解C ++中的模板;记住,set<T>被模仿&#34;在T上,即T是您在使用集时指定的任何内容。 (字符串(set<string>,您的自定义结构(set<MyStruct>)等)在set的实现中,您可以想象一个帮助器类,如:

template<typename T>
struct node {
    T value;
    node<T> *left, *right;
}

此结构包含一个值,哪个节点位于其左侧和右侧。 set<T>,因为它有T在其实施中使用,可以使用它来将此node结构模板化为同一T。在我的例子中,标记为&#34; dog&#34;将是node,其中valuestd::string,其值为"dog"left指向持有"cat"的节点,右指向到持有"duck"的节点。

当您在集合中查找值时,它会从树中查找值,从根开始。该集可以&#34;知道&#34;通过比较您正在寻找/插入/删除的值与它正在查看的节点,可以选择哪种方式(左或右)。 (对于集合的要求之一是,无论您将其模板化为与<相比,还是为其提供了代替<的函数:所以,int有效, std::string有效,MyStruct可以定义<或编写&#34;比较器&#34 ;.

答案 3 :(得分:-2)

无论如何,你总是可以通过比较它们的字节数来比较两种颜色。

因此,如果集合表示为已排序的二叉树,则结果为-1的memcmp表示向左插入,而带有+1的表示向右插入。

<强>后来

我非常渴望表明没有必要根据set元素的位来分支,我没有考虑到需要std :: set元素来实现operator

我原谅了吗?