我想知道对于具有以下要求的集合,最通用的树结构是什么:
树结构需要支持有效的插入和删除,以及通过唯一ID快速查找。此外,找到第一个可用的未使用的唯一ID需要快速操作。
哪种树最适合这些要求?
编辑:这棵树只会留在内存中;在任何时候它都不会被持久化到磁盘上。我不需要担心碰到磁盘,磁盘缓存或任何类似的东西。这也是我不打算使用像SQLite这样的东西的原因。
答案 0 :(得分:5)
根据您需要多快的速度,您可能只需将整个内容视为单个内存中的表格映射到文件中。寻址是通过直接计算。您可以简单地链接空闲插槽,这样您就可以确切地知道下一个空闲插槽的确切位置。大多数访问最多有1或2个磁盘访问(取决于底层文件系统要求)。在机器上放置一块内存,你可能根本不会碰到磁盘。
我知道这听起来很蛮力,但你会惊讶它的速度有多快。
更新以回应:“我不是在寻找磁盘可持续解决方案”
好吧,如果你真的要在这个结构中有多达2 ^ 32个项目(时间有多大),那么你需要在机器上有足够的内存来容纳这只小狗,否则内核会开始交换东西内存和内存为你。这仍然转化为击中磁盘。如果你让它交换,不要忘记检查交换区域的大小,你很可能不得不碰它。使用mmap(或类似的东西)有点像创建自己的私有交换区域,它可能对在同一系统上运行的其他进程的影响较小。
我会注意到,一旦这个东西超过你的可用物理内存(无论你是使用交换空间还是mmap或B-tree还是Black-Red或可扩展哈希或其他什么),理解这一点至关重要 你的访问模式。如果你在整个地方进行跳房子游戏,那么你将很多地点击磁盘 。使用像B树(或几个类似结构中的任何一个)之类的结构的主要原因之一是树的顶层(包含索引)倾向于留在内存中(因为大多数分页算法使用{{3当你触摸一个叶子页面时,你只会吃一个磁盘访问。
底线:它既可以适合内存,也可以不适合。如果没有,则您的10 ^ -9秒内存访问将变为10 ^ -3磁盘访问。即慢一百万倍。 LRU
答案 1 :(得分:2)
我会选择红黑树,因为它会在插入时平衡树以确保最佳插入/删除/检索。 AVL树是一个选项,但插入速度稍慢,因为它在插入时的平衡更加严格。
答案 2 :(得分:2)
您是否考虑过类似trie的内容?查找在密钥长度上是线性的,在您的情况下意味着基本上是不变的,并且由于节点共享公共子串,存储可以更紧凑。
请记住,如果您的数据集实际上填满了大量的密钥空间,那么更大的效率问题可能是缓存和磁盘访问,而不是查找。
答案 3 :(得分:1)
我的反应会告诉我达到标准实现,例如stl中的实现。但是假设你有理由实现自己的,我通常会选择Red-Black Trees,它在所有操作中都表现良好。或者,我会尝试splay树,它可以非常快但具有摊销的复杂性,即某些单独的操作可能需要更长的时间。
远离AVL树,因为您需要进行大量更新。 AVL树很适合你有很多查找但很少更新,因为更新可能相当慢。
答案 4 :(得分:1)
您希望您的树真正拥有2 ^ 32-1个条目吗?即使只有一半,我肯定会尝试使用SQLite。您可能能够将其全部放入内存中,但如果您翻页一次,数据库将更快。数据库旨在有效地处理大量数据集,尤其是当整个集合不能同时适应内存时。
我确实打算自己这样做,查看一些数据库代码并使用BTree。使用较小的数据集时,红黑会更快,但是如果有这么多数据,你的瓶颈就不会是处理器速度,而是内存和硬盘速度。
所有这一切都说我无法想象一个大有用的指针图。你只需要存储地图就可以推动现代记忆的极限。你不会遗留任何东西让地图指向。
答案 5 :(得分:0)
boost::unordered_map
已经摊销了常量时间插入,删除和查找。它是您描述的最佳数据结构。
它的唯一不足之处在于,正如名字所说的那样,它是无序的。如果你真的不走运,如果每一个哈希都发生冲突,它最终会成为线性时间。但是,使用boost的默认boost :: hash函数可以很容易地避免这种情况。另外,散列整数是微不足道的;所以最糟糕的情况不会发生在你身上。
(注意:它不是树而是哈希表,你专门要求“树”。也许你认为最有效的方式是某种树(它不是)?)
答案 6 :(得分:0)
为什么要一棵树?
对我来说,似乎你需要一个数据库。如果您期望节点数量较少,则哈希表就足够了。
我要警告你关于记忆的事。如果你填满整个树(2 ^ 32项)你需要4千兆字节的值本身另外8GB的指针。如果可能的话,请考虑数据库。
答案 7 :(得分:0)
每个项目由32位标识表示,它是密钥和两个指针。指针是否与树相关联,还是与身份有关?
如果他们只是实施树的一部分,就抛弃它们。你不需要它们。在一个非常大的位图中表示数字是否存在。找到最低的未使用位并不快,但我不认为它可以。它只有大约512M的主内存,这并不是那么糟糕。
如果指针是有意义的数据,请使用数组。无论如何,您将不得不为四个giganodes和指针分配空间来组成地图,因此为四个giganodes分配空间,并为每个节点是否处于活动状态分配一个指示器。使用memset()
将整个事物设置为零,并保留最低未使用节点指针。用它来添加节点。删除节点时,将其标记为未使用,并使用指针维护双向链接空闲列表。您将不得不找到下一个较低的未使用节点,这可能需要一段时间,但我再也看不到如何保持这个速度。 (如果您只需要一个未使用的节点,而不是最低节点,只需将释放的节点放在某个空闲列表中。)
这可能需要大约64G或96G的RAM,但这比地图解决方案要少。