【问题标题】:Advanced data structures in practice实践中的高级数据结构
【发布时间】:2010-09-28 04:50:09
【问题描述】:

在我从事编程的 10 年中,我一方面可以数出我使用过的数据结构的数量:数组、链表(我将堆栈和队列与此混为一谈)和字典。这并不奇怪,因为我编写的几乎所有应用程序都属于表单数据/CRUD 类别。

我从来不需要使用红黑树、跳过列表、双端队列、循环链表、优先级队列、堆、图或在过去 50 年。我觉得我错过了。

这是一个开放式问题,但这些“异国情调”的数据结构在实践中在哪里使用?有没有人有使用这些数据结构解决特定问题的实际经验?

【问题讨论】:

    标签: data-structures


    【解决方案1】:

    一些例子。它们含糊不清,因为它们是为雇主工作的:

    • heap 在 Google 风格的搜索中获得前 N 个结果。 (从索引中的候选者开始,线性地遍历它们,通过最大大小为 N 的最小堆筛选它们。)这是用于图像搜索原型的。

    • Bloom filters 将有关数百万用户看到的某些数据的大小缩减到适合现有服务器的数量(为了速度,所有这些数据都必须在 RAM 中);最初的设计需要很多新的服务器来处理这个数据库。

    • triangular array representation 将推荐引擎的密集对称数组的大小减半(出于同样的原因,再次使用 RAM)。

    • 必须根据某些关联对用户进行分组; union-find 让这一切变得简单、快速、准确,而不是缓慢、笨拙和近似。

    • 一个用于根据附近人们的驾驶时间选择零售站点的应用程序使用Dijkstra shortest-path 和优先队列。其他 GIS 工作利用了 quadtreesMorton 索引。

    了解数据结构领域的内容会派上用场——“在实验室呆上几周可以节省你在图书馆里呆的时间”。布隆过滤器案例之所以有价值,只是因为它的规模:如果问题是在初创公司而不是雅虎出现的,我会使用一个普通的旧哈希表。我认为其他示例在任何地方都是合理的(尽管现在您不太可能自己编写代码)。

    【讨论】:

    • 我的问题在 OP 中没有真正的“答案”,但我只是觉得这篇文章特别简洁:)
    【解决方案2】:

    B-trees 在数据库中。

    R-trees 用于地理搜索(例如,如果我有 10000 个形状,每个形状都有一个散布在二维平面周围的边界框,那么这些形状中的哪一个与任意边界框 B 相交?)

    dequesC++ STL 中的形式是可增长的向量(比链表更节省内存,并且在中间“窥视”任意元素的恒定时间)。据我所知,我从未完全使用过双端队列(从两端插入/删除),但它足够通用,您可以将其用作堆栈(从一端插入/删除)或队列(插入一端,从另一端删除),并且还具有查看中间任意元素的高性能访问权限。

    我刚读完Java Generics and Collections——“泛型”部分让我头疼,但集合部分很有用,它们指出了跳过列表和树之间的一些区别(两者都可以实现映射/集合) :跳过列表为您提供了从一个元素到下一个元素的内置恒定时间迭代(树是 O(log n) ),并且在多线程情况下实现无锁算法要简单得多。

    优先级队列用于调度其他事情(这里有一个webpage,简要讨论了应用程序);堆通常用于实现它们。我还发现堆排序(至少对我而言)是最容易理解和实现的 O(n log n) 排序。

    【讨论】:

      【解决方案3】:

      它们经常在图书馆的幕后使用。例如,有序字典数据结构(即允许按键排序遍历的associative array)很可能不使用red-black tree. 实现

      许多数据结构(想到splay trees)因其在某些情况下的最佳行为(temporal locality of reference 在伸展树的情况下)而受到关注,因此它们主要与这些情况下的使用相关。在大多数情况下,了解这些数据结构的真正好处是能够在正确的情况下使用它们,并对它们的行为有合理的理解。

      以排序为例:

      • 在大多数情况下quicksort 或修改后的快速排序 到另一种方法时 个别部分变得足够小 通常是最快的排序 大多数用途的算法。 然而,快速排序往往显示 次优行为 几乎排序的数据。

      • heap sort 的主要优点是它可以在 具有最小中间体的原位 存储,这使得它非常好 用于内存受限的 系统。虽然速度较慢 平均而言(尽管仍然是 n log(n)), 它不会受苦 从糟糕的最坏情况表现 快速排序。

      • 第三个例子是merge sort,可以这样做 依次,使其成为最好的 对数据集进行排序的选择很多 比你的主内存大。 这个的另一个名字是 '外部排序',意思是你可以 使用外部存储(磁盘或 磁带)以获得中间结果。

      【讨论】:

        【解决方案4】:

        这取决于您工作的抽象级别。

        我知道我和你有类似的经历。在大多数软件开发的当前抽象级别。字典和列表是我们使用的主要数据结构。

        我认为,如果您低头查看较低级别的代码,您会看到更多“异国情调”的数据结构。

        【讨论】:

        • 我同意。考虑到我的代码在软件堆栈中的位置有多高,如果有我需要的数据结构并且它不存在于我的代码下方的现有库中,那么这通常是库的缺点。
        【解决方案5】:
        【解决方案6】:

        我认为您看到大多数高级算法都使用了花哨的数据结构。我想到的主要示例是 A*,它使用由堆实现的图形和优先级队列。

        【讨论】:

          【解决方案7】:

          在金融领域,您需要使用树来计算依赖于许多其他动态值的工具的价值。电子表格具有类似的依赖关系树,编译器在翻译成机器代码之前会创建一个抽象语法树。

          【讨论】:

            【解决方案8】:

            Fibonacci heaps 用于高效实现Dijkstra's algorithm

            【讨论】:

            【解决方案9】:

            是的,有时。我看到的问题是,许多人虽然知道它们,但他们不知道如何真正应用它们。大多数人恢复到数组链接列表等。在大多数情况下,他们将作为更高级的数据结构完成工作(有时你真的必须“踢”到位),他们只是效率较低。人们倾向于做对他们来说更容易的事情,但这不一定是做某事的最佳方式。我不能责怪他们,我相信我也这样做,但这就是为什么你在编程中看不到很多“高级”概念的原因。

            【讨论】:

              【解决方案10】:

              我使用循环链表来实现队列(用 C 语言),我将永远迭代这些队列,即网络连接队列。

              但是我发现,当我使用高级语言时,我发现自己不会费心以这种方式实现队列,因为我可以动态地扩大和缩小列表,而不必过多担心它。当然,这样做是有性能代价的,因为我对何时发生内存分配的控制较少,但这是我们为能够拥有非常灵活的列表而付出的代价之一。

              【讨论】:

                【解决方案11】:

                当代码需要时,您会倾向于看到更复杂的数据结构。通常,当您在较低级别处理更复杂的代码时,我会看到这一点,即在核心操作系统中,编写类库的基本部分(实现字符串、数组等),编写高性能或多线程代码等。我认为它们发挥重要作用的另一个地方是在实现特定算法时,搜索、采样、统计分析、优化等算法通常是在考虑特定数据结构的情况下编写的。

                【讨论】:

                  【解决方案12】:

                  我经常使用集合、排序集合(始终保持其元素排序,并支持快速元素插入)和惰性列表。

                  【讨论】:

                    【解决方案13】:

                    平衡树(红黑等)通常用于抽象数据类型的实现。

                    只有比较少的抽象数据类型,比如

                    • 列表
                    • 地图
                    • 有序地图
                    • 多地图
                    • 有序多图
                    • 优先队列(看起来很像一个有序的多地图)

                    同样,集合看起来很像地图,但您不需要值,只需要键。

                    我不时发现其中大部分很有用;优先级队列是一种非常有用的数据结构,在各种算法中都有应用(例如调度、路径查找等)。

                    您说“字典”,您可能指的是地图或有序地图。

                    一些地图是无序的(通常实现为哈希) - 这是有序地图的有用子集。

                    【讨论】:

                      【解决方案14】:

                      我使用了一个循环列表进行缓存。

                      C++ 类模板提供了获取对象的接口 (Cache<Obj, Len>)。它的几个实例返回不同类型的“屏幕”,就像在图形界面的不同视图中一样。在幕后,如果请求的“屏幕”不可用,它会被创建(昂贵的操作)并推送到环形缓冲区的头部,将最旧的一个推出(卸载其纹理等)。

                      因此,在始终从硬盘读取一堆图像文件与仅将所有图像加载到 RAM 中并永久保存它们之间实现了折衷。折衷由各种缓冲区的长度控制。

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2016-09-21
                        • 1970-01-01
                        • 2018-04-26
                        • 2023-04-08
                        • 1970-01-01
                        • 2017-08-22
                        • 1970-01-01
                        • 2017-07-02
                        相关资源
                        最近更新 更多