我理解STL set基于二叉搜索树的抽象数据结构。那么底层数据结构是什么?是一个数组吗?
此外,集合的insert()函数如何工作?集合如何检查元素是否已经存在于其中?
我在维基百科上读到另一种实现集合的方法是使用哈希表。这是如何工作的?
调试进入 g++
6.4 stdlibc++源码
你是否知道在 Ubuntu 16.04 默认的 g++-6
软件包或者从源代码构建的 GCC 6.4中,无需任何其他设置即可进入 C++ 库?
通过这样做,我们很容易得出结论:此实现中使用了红黑树。
这是有道理的,因为 std::set
可以按顺序遍历,如果使用哈希映射,则效率会降低。
main.cpp
#include <cassert>
#include <set>
int main() {
std::set<int> s;
s.insert(1);
s.insert(2);
assert(s.find(1) != s.end());
assert(s.find(2) != s.end());
assert(s.find(3) == s3.end());
}
编译和调试:
g++ -g -std=c++11 -O0 -o main.out main.cpp
gdb -ex 'start' -q --args main.out
现在,如果你进入s.insert(1)
,你会立即到达/usr/include/c++/6/bits/stl_set.h
:
487 #if __cplusplus >= 201103L
488 std::pair<iterator, bool>
489 insert(value_type&& __x)
490 {
491 std::pair<typename _Rep_type::iterator, bool> __p =
492 _M_t._M_insert_unique(std::move(__x));
493 return std::pair<iterator, bool>(__p.first, __p.second);
494 }
495 #endif
这明显只是将 _M_t._M_insert_unique
转发。
因此,我们在 vim 中打开源文件并找到 _M_t
的定义:
typedef _Rb_tree<key_type, value_type, _Identity<value_type>,
key_compare, _Key_alloc_type> _Rep_type;
_Rep_type _M_t; // Red-black tree representing set.
因此,_M_t
的类型是_Rep_type
,而_Rep_type
是一个_Rb_tree
。
好的,现在这已经足够证明了。如果您不相信_Rb_tree
是一种红黑树,请再进一步阅读算法。
unordered_set
使用哈希表
同样的过程,但是将代码中的set
替换为unordered_set
。
这是有道理的,因为std::unordered_set
不能按顺序遍历,所以标准库选择了哈希表而不是红黑树,因为哈希表具有更好的平均插入时间复杂度。
进入insert
会导致/usr/include/c++/6/bits/unordered_set.h
:
415 std::pair<iterator, bool>
416 insert(value_type&& __x)
417 { return _M_h.insert(std::move(__x)); }
我们在vim
中打开源文件,并搜索_M_h
:
typedef __uset_hashtable<_Value, _Hash, _Pred, _Alloc> _Hashtable;
_Hashtable _M_h;
所以使用哈希表吧。
std::map
和std::unordered_map
std::set
与std:unordered_set
类似:C++标准库中的std::map使用的是什么数据结构?
性能特征
您还可以通过计时来推断使用的数据结构:
图形生成过程和堆栈和二叉搜索树分析详见:Heap vs Binary Search Tree (BST)
我们清楚地看到:
std::set
,插入时间对数级别std::unordered_set
,更复杂的哈希表模式:std::map
快得多,除了非常小的地图大小std::set
的实现方式可能会有所不同--C++标准只是指定了一个抽象数据类型。换句话说,标准并没有规定容器应该如何实现,而是规定了它需要支持哪些操作。然而,据我所知,STL的大多数实现都使用红黑树或其他平衡二叉搜索树(例如GNU libstdc++使用红黑树)。Node
数据结构。std::set
是有序的。 - Ciro Santilli OurBigBook.comstd::less
的Compare
,而不是从bool(T, T)
中构建一个std::size_t(T)
。 - Caleth你可以通过首先定义一个 Node
结构来实现二叉搜索树:
struct Node
{
void *nodeData;
Node *leftChild;
Node *rightChild;
}
那么,您可以使用另一个Node *rootNode;
来定义树的根节点。
二叉搜索树的维基百科条目有一个非常好的示例,展示了如何实现插入方法,我也建议您查看一下。
关于重复项,通常在集合中不允许存在重复项,因此您可以根据您的规范丢弃该输入、抛出异常等。
特定容器在C++中的实现完全取决于具体实现。所需的只是结果符合标准规定的要求,例如各种方法的复杂度要求、迭代器要求等。
集合通常被实现为红黑树。
我查过了,无论是 libc++
还是 libstdc++
都使用红黑树来实现 std::set
。
std::unordered_set
在 libc++
中使用哈希表实现,我认为在 libstdc++
中也是这样,但没有检查。
编辑: 显然我的话不足以说服人。
libstdc++
进行了确认:https://dev59.com/m3E85IYBdhLWcg3w432o#51944661,因为我只相信来自GDB本身的结果 :-) - Ciro Santilli OurBigBook.com