什么是鲜为人知但有用的数据结构?


795

周围有一些确实有用的数据结构,但大多数程序员都不知道。他们是哪一个?

每个人都知道链表,二进制树和哈希,但是例如跳过列表Bloom过滤器呢。我想了解更多不是很常见的数据结构,但是值得一读,因为它们依赖于出色的构想并丰富了程序员的工具箱。

PS:我对诸如跳舞链接之类的技术也很感兴趣,这些技术巧妙地利用了通用数据结构的属性。

编辑:请尝试包含指向更详细描述数据结构的页面的链接。另外,尝试添加一些关于数据结构为何如此酷的词(如JonasKölker所指出的)。另外,尝试为每个答案提供一种数据结构。这将使更好的数据结构仅凭其投票就可以浮动到顶部。


Answers:


271

尝试树(也称为前缀树或临界位树)已经存在40多年了,但仍然相对未知。在“ TRASH-动态LC-trie和哈希数据结构 ”中介绍了尝试的一种非常酷的用法,它结合了trie和哈希函数。


12
非常常用的拼写检查工具
Steven A. Lowe,2009年

突发尝试也是一个有趣的变体,其中您仅将字符串的前缀用作节点,否则将字符串列表存储在节点中。
Torsten Marek

Perl 5.10中的正则表达式引擎会自动创建尝试。
布拉德·吉尔伯特

以我的经验,由于指针通常比字符长,这很可惜,尝试很昂贵。它们仅适用于某些数据集。

18
由于没有主题问题,所以没有人提及jQuery,所以没有这样的问题。.jQuery的创建者John Resig 撰写了
Oskar Austegard

231

布隆过滤器m位的位数组,最初都设置为0。

要添加项目,请通过k个哈希函数运行该项目,该函数将为您提供数组中的k个索引,然后将其设置为1。

要检查某项是否在集合中,请计算k个索引并检查它们是否都设置为1。

当然,这提供了一些假阳性的可能性(根据Wikipedia,大约为0.61 ^(m / n),其中n是插入项的数量)。假阴性是不可能的。

删除项目是不可能的,但是您可以实现计数布隆过滤器(由整数数组和增量/减量表示)。


20
你忘了提到其使用使用词典:)你可以挤出一个完整的字典到布隆过滤器有512K左右,像一个哈希表,而不值
克里斯小号

8
Google引用在BigTable的实现中使用Bloom过滤器。
Brian Gianforcaro 2010年

16
@FreshCode实际上,它可以让您便宜地测试集合中是否缺少元素,因为您可以得到假阳性但绝不能假阴性
Tom Savage 2010年

26
@FreshCode就像@Tom Savage所说的那样,在检查底片时更有用。例如,您可以将其用作快速且小型(就内存使用而言)的拼写检查器。向其中添加所有单词,然后尝试查找用户输入的单词。如果得到负数,则表示它拼写错误。然后,您可以运行一些更昂贵的检查以找到最接近的匹配项并提供更正。
lacop 2010年

5
@ abhin4v:当大多数请求可能返回答案为“否”(例如此处的情况)时,通常使用布隆过滤器,这意味着可以通过较慢的精确测试来检查少量的“是”答案。这仍然导致平均查询响应时间大大减少。不知道Chrome的安全浏览是否可以做到这一点,但这是我的猜测。
j_random_hacker 2010年

140

Rope:这是一个允许便宜的前缀,子字符串,中间插入和追加的字符串。我实际上只使用过一次,但是没有其他结构可以满足要求。常规的字符串和数组前置对于我们需要做的事情来说太昂贵了,而反转一切都不是问题。


我曾经想到过类似的东西供自己使用。很高兴知道它已经在其他地方实现。
Kibbee

15
SGI STL(1998)中有一个实现:sgi.com/tech/stl/Rope.html
夸克

2
不知道什么叫我最近写了一篇非常类似于这样的Java -性能一直很好: code.google.com/p/mikeralib/source/browse/trunk/Mikera/src/...
mikera


6
Mikera的链接是陈旧的,这是最新的
aptwebapps 2011年

128

跳过列表非常简洁。

Wikipedia
跳过列表是一种概率数据结构,它基于多个并行的排序链表,其效率可与二叉搜索树相比(大多数操作的顺序日志为平均时间)。

它们可以用作平衡树的替代方法(使用概率平衡而不是严格执行平衡)。它们比一棵红黑树更容易实现并且速度更快。我认为他们应该成为每个优秀程序员的工具。

如果要深入了解跳过列表,请访问MIT的算法入门讲座视频链接

另外,是一个Java小程序,以可视方式演示了跳过列表。


+1 Qt将跳过列表而不是RB树用于已排序的地图和集合。是的,它们很漂亮(无论如何,都是命令式语言)。
Michael Ekstrand 2010年

2
Redis使用跳过列表来实现“排序集”。
antirez 2011年

当我需要一个良好的数据结构并且无法保证数据的顺序时,跳过列表可能是我最喜欢的数据结构,并且我想要一个比其他“平衡”数据结构更简单的实现。真好
Earino 2011年

有趣的旁注:如果您在跳过列表中添加了足够的级别,则最终会得到B树。
里亚德·卡拉

92

空间索引,尤其是R树KD树,可以有效地存储空间数据。它们适用于地理坐标数据和VLSI放置和路线算法,有时还可以用于最近邻搜索。

位阵列紧凑地存储各个位,并允许快速位操作。


6
空间索引对于涉及诸如重力的远程力的N体模拟也很有用。
贾斯汀·皮

87

拉链 -数据结构的派生词,它修改结构以使其具有自然的“光标”概念(当前位置)。这些功能非常有用,因为它们可以确保索引不会超出范围-例如在 xmonad窗口管理器中使用,以跟踪哪个窗口聚焦了。

令人惊讶的是,您可以通过将微积分技术应用于原始数据结构的类型来派生它们!


2
这仅在函数式编程中有用(在命令式语言中,您只需保留一个指针或一个索引)。另外,我仍然不了解拉链的工作原理。
Stefan Monov 2010年

4
@Stefan的要点是,您现在不需要保留单独的索引或指针。
唐·斯图尔特

69

这里有一些:

  • 后缀尝试。适用于几乎所有类型的字符串搜索(http://en.wikipedia.org/wiki/Suffix_trie#Functionality)。另请参见后缀数组;它们的速度不如后缀树,但要小得多。

  • 八叉树(如上所述)。他们很酷的原因有三点:

    • 它们很小:您只需像在任何二叉树中一样需要左右指针(无需存储节点颜色或大小信息)
    • 它们(相对)非常容易实现
    • 它们为整个“测量标准”(登录n查找时间是每个人都知道的)提供了最佳的摊销复杂性。看到http://en.wikipedia.org/wiki/Splay_tree#Performance_theorems
  • 堆排序的搜索树:您在树中存储了一堆(键,PRIO)对,这样一来,这是一个相对于键的搜索树,而相对于优先级而言是堆排序的。可以证明这样的树具有独特的形状(并且并不总是完全包装在左上方)。使用随机优先级,它可以为您提供预期的O(log n)搜索时间IIRC。

  • 一个利基市场是具有O(1)邻居查询的无向平面图的邻接表。这不是数据结构,而是组织现有数据结构的特定方式。操作方法如下:每个平面图都有一个度数最多为6的节点。选择一个这样的节点,将其邻居放置在其邻居列表中,将其从图中删除,然后递归直到图为空。给定一对(u,v)时,在v的邻居列表中查找u,并在u的邻居列表中查找v。两者的大小最大为6,因此这是O(1)。

通过上述算法,如果u和v是邻居,则不会在v列表中同时包含u和在u列表中包含v。如果需要此功能,只需将每个节点缺少的邻居添加到该节点的邻居列表中,但要存储需要查找的邻居列表中的多少才能快速查找。


堆有序搜索树称为treap。您可以使用这些技巧之一是更改节点的优先级,以将其推入易于删除的树的底部。
paperhorse

1
“堆有序搜索树被称为挖掘。” -在我所听到的IIRC定义中,陷阱是具有随机优先级的按堆排序的搜索树。您可以根据应用程序选择其他优先级...
JonasKölker2009年

2
后缀特里几乎与更酷的后缀相同,但后缀的边缘没有字符串,并且没有单独的字母,可以在线性时间(!)中构建。同样,尽管渐近变慢,但实际上对于许多任务,后缀数组通常比后缀树快得多,这是因为后缀数组的大小较小且指针指向较少。喜欢O(1)平面图查询!
j_random_hacker 2010年

@j_random_hacker:后缀数组并不是渐近变慢。这是线性后缀数组构造的约50行代码:cs.helsinki.fi/u/tpkarkka/publications/icalp03.pdf
Edward KMETT,2010年

1
@Edward Kmett:实际上我已经读过该论文,这在后缀数组构造方面是一个相当大的突破。(尽管已经知道可以通过“通过”后缀树来进行线性时间构造,但这是第一个不可否认的实用“直接”算法。)但是,除非有LCA,否则构造后的某些操作在后缀数组上仍会渐近地变慢。表也​​已建立。这也可以在O(n)中完成,但是这样做会失去纯后缀数组的大小和位置优势。
j_random_hacker 2010年

65

我认为标准数据结构的无锁替代方案(即无锁队列,堆栈和列表)被忽略了很多。
随着并发成为更高的优先级,并且与使用Mutex或锁来处理并发读/写相比,它们的目标越来越令人钦佩,它们变得越来越重要。

这是一些链接
http://www.cl.cam.ac.uk/research/srg/netos/lock-free/
http://www.research.ibm.com/people/m/michael/podc-1996.pdf [链接到PDF]
http://www.boyet.com/Articles/LockfreeStack.html

迈克·阿克顿Mike Acton)的博客(通常是挑衅性的)上有一些关于无锁设计和方法的优秀文章。


在当今的多核,非常并行,可扩展性上瘾的世界中,无锁替代品是如此重要:-)
Earino 2011年

好吧,在大多数情况下,干扰者实际上会做得更好。
deadalnix

55

对于需要将一堆项目划分为不同的集合和查询成员身份的情况,我认为Disjoint Set非常漂亮。联合和查找操作的良好实施会导致摊销成本实际上是恒定的(如果我正确记得我的数据结构类,则为Ackermnan函数的逆函数)。


8
这也称为“联合查找数据结构”。当我第一次在算法类中了解这种聪明的数据结构时,我感到敬畏...
BlueRaja-Danny Pflughoeft

union-find-delete扩展名也允许恒定时间删除。
Peaker

4
我用了一个分离集为我的地牢发电机,以确保所有的房间可以连通道:)
黄金比例

52

斐波那契堆

在许多已知的最快算法中(渐近地)将它们用于许多与图相关的问题,例如最短路径问题。Dijkstra的算法使用标准二进制堆以O(E log V)的时间运行;使用斐波那契堆可以将其提高到O(E + V log V),这对于密集图是极大的加速。但是,不幸的是,它们具有较高的恒定因子,通常使它们在实践中不切实际。


正如您所说的,恒定因子很高,而根据一位不得不这样做的朋友很难实现。从父亲的角度来看,它并不酷,但仍然值得一读。
p4bl0

与其他堆相比,这些人在这里具有竞争优势:cphstl.dk/Presentation/SEA2010/SEA-10.pdf 有一个名为Pairing Heaps的相关数据结构更易于实现,并提供了相当不错的实用性能。但是,理论分析是部分开放的。
曼努埃尔

从我对Fibonacci堆的经验中,我发现内存分配的昂贵操作使其效率不如数组支持的简单二进制堆。
jutky 2012年

44

具有3D渲染经验的任何人都应该熟悉BSP树。通常,通过知道相机的坐标和方位,通过构造3D场景使其易于渲染的方法。

二进制空间划分(BSP)是一种通过超平面将空间递归细分为凸集的方法。该细分借助称为BSP树的树数据结构来表示场景。

换句话说,这是一种将形状复杂的多边形分解为凸集或完全由非反射角度(小于180°的角度)组成的较小多边形的方法。有关空间分区的更一般说明,请参见空间分区。

最初,这种方法是在3D计算机图形学中提出的,以提高渲染效率。其他一些应用程序包括在CAD中执行具有形状(构造实体几何形状)的几何运算,在机器人技术和3D计算机游戏中进行碰撞检测以及涉及处理复杂空间场景的其他计算机应用程序。


...以及相关的八叉树和kd树。
Lloeki 2011年


38

看看手指树,尤其是如果您喜欢前面提到的纯功能数据结构。它们是持久性序列的功能性表示,支持以摊销的恒定时间访问末端,并以较小部分的对数进行时间的对数级联和拆分。

按照 原始文章

我们功能性的2-3指树是Okasaki(1998)引入的一种通用设计技术的实例,称为 隐式递归减速。我们已经注意到,这些树是他隐式双端队列结构的扩展,用2-3个节点替换了对,以提供有效级联和拆分所需的灵活性。

手指树可以使用一个monoid进行参数化,并且使用不同的monoid将导致该树的行为不同。这使Finger Trees可以模拟其他数据结构。



看看这个重复的答案,值得一读!
Francois G


33

我很惊讶没有人提到默克尔树(即哈希树))。

在许多情况下(P2P程序,数字签名)使用,当您只有一部分文件可用时,您要验证整个文件的哈希。


32

<zvrba> Van Emde-Boas树

我想知道为什么会很有用他们很酷。通常,“为什么”是最重要的问题;)

我的回答是,它们为您提供具有{1..n}键的O(log log n)字典,与正在使用的键数无关。就像重复的减半给您O(log n)一样,重复的sqrting也会给您O(log log n),这就是vEB树中发生的情况。


从理论上讲,它们很好。但是,在实践中,要从中获得竞争绩效非常困难。我知道的论文可以使它们在32位密钥上正常工作(citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.2.7403),但是这种方法的扩展范围可能不会超过34-35位或因此,没有任何实现。
曼努埃尔

它们之所以很酷的另一个原因是,它们是许多忽略缓存的算法的关键构建块。
爱德华·KMETT


29

哈希表的一个有趣的变体称为Cuckoo Hashing。它使用多个哈希函数而不是1个哈希函数来处理哈希冲突。通过从主哈希指定的位置删除旧对象,然后将其移动到备用哈希函数指定的位置,可以解决冲突。杜鹃散列可以更有效地利用内存空间,因为仅使用3个散列函数就可以将负载系数提高到91%,并且仍然具有良好的访问时间。


5
Check Hopscotch散列声称速度更快。
chmike


26

我喜欢Cache Oblivious数据结构。基本思想是在递归的较小块中布置一棵树,以便许多不同大小的缓存将利用方便放入其中的块的优势。这样就可以在从RAM中的L1缓存到从磁盘读取的大块数据的所有内容中高效使用缓存,而无需了解任何这些缓存层大小的细节。


有趣的是,该链接的转录是:“关键是van Emde Boas布局,以Peter van Emde Boas于1977年构思的van Emde Boas树数据结构命名”
sergiol 2012年

23

左倾斜的红黑树。罗伯特·塞奇威克(Robert Sedgewick)于2008年发布了一个大大简化的红黑树实现(〜减少了一半的代码行)。如果您在解决Red-Black树的实现时遇到麻烦,请阅读此变体。

与Andersson树非常相似(如果不相同)。



19

GerthStøltingBrodal和Chris Okasaki的引导式偏斜二项式堆

尽管名称很长,但是即使在函数设置中,它们也可以提供渐近最优的堆操作。

  • O(1)大小,并集,插入,最小值
  • O(log n) deleteMin

请注意,与花费在数据结构教科书中通常涵盖的更知名的堆(如左派堆)不同,并集需要花费的时间O(1)而不是O(log n)时间。而且与斐波那契堆不同,即使持续使用,这些渐近也是最坏的情况,而不是摊销!

Haskell中有多种 实现

它们由Brodal和Okasaki联合派生,在Brodal提出了具有相同渐近性的命令式堆之后


18
  • 实时光线追踪(以及其他)中使用的空间数据结构Kd-Trees的缺点是,与不同空间相交的三角形需要被裁剪。通常,BVH更快,因为它们更轻巧。
  • MX-CIF四叉树,通过将常规四叉树与四叉树边缘上的二叉树结合起来,存储边界框而不是任意点集。
  • HAMT,由于涉及的常数,具有访问时间通常超过O(1)哈希图的分层哈希图。
  • 倒排索引,在搜索引擎领域众所周知,因为它用于快速检索与不同搜索词相关的文档。

如果不是全部,大多数(如果不是全部)都记录在《 NIST 算法和数据结构词典》中



17

并不是真正的数据结构;优化动态分配数组的更多方法,但是Emacs中使用的间隙缓冲区很酷。


1
我绝对会认为这是一个数据结构。
Christopher Barber

对于任何有兴趣的人来说,这也是实现支持Swing文本组件的Document(例如,PlainDocument)模型的确切方式。在1.2之前,我认为文档模型是直线数组,这会导致大型文档的插入性能糟糕;当他们搬到Gap Buffers之后,一切都又回到了世界。
里亚德·卡拉

16

芬威克树。它是一种数据结构,用于在两个给定的子索引i和j之间对向量中所有元素的总和进行计数。简单的解决方案,因为从开始就不允许预先计算总和,因此不允许更新项目(您必须做O(n)才能跟上进度)。

Fenwick树允许您在O(log n)中进行更新和查询,它的工作原理非常酷而简单。Fenwick的原始论文对此作了很好的解释,可从此处免费获得:

http://www.cs.ubc.ca/local/reading/proceedings/spe91-95/spe/vol24/issue3/spe884.pdf

它的父亲RQM树也很酷:它允许您保留关于向量的两个索引之间的最小元素的信息,并且还可以用于O(log n)更新和查询。我喜欢先教RQM,然后教Fenwick树。


恐怕这是重复的。也许您想添加到上一个答案?
Francois G

分段树也与此相关,这对于执行各种范围查询很有用。
dhruvbird 2011年


13

嵌套集非常适合表示关系数据库中的树并在它们上运行查询。例如,ActiveRecord(Ruby on Rails的默认ORM)带有一个非常简单的嵌套集插件,这使得处理树变得微不足道。


12

它是特定于域的,但半边数据结构却非常整洁。它提供了一种在多边形网格(面边)上进行迭代的方法,这在计算机图形和计算几何中非常有用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.