大多数数据结构都可以使用向量实现吗?

Can most of the data structures be implemented using vectors?

本文关键字:实现 向量 数据结构 可以使 大多数      更新时间:2023-10-16

我使用C++向量来实现堆栈,队列,堆,优先级队列和有向加权图。在书籍和参考文献中,我看到了这些数据结构的大类,所有这些都可以使用向量简单地实现。(使用指针可能会有更大的灵活性)

我们甚至可以使用向量实现高级数据结构吗?
如果是,为什么C++本书仍然使用指针来解释长类的概念?

是要记住较低层次的想法,如果这样更生动,还是让学生配备这样的指针使用?

的确,许多数据结构可以在向量(数组,为了这个答案)之上实现,基本上所有这些都可以,因为每个计算任务都可以实现为在具有更基本数据访问能力的图灵机上运行(或者,在现实世界中,你可以说你用指针实现的任何程序最终都会在具有简单数组虚拟内存空间的 CPU 上运行, 所以你可以称之为一个巨大的数组)。然而,它并不总是聪明的。主要原因有二:

  1. 性能/时间复杂度 - 向量根本无法提供 O(1) 中的所有基本运算。有一个快速初始化的解决方案,但尝试将值随机插入到一个大向量中,看看你的表现有多糟糕 - 那是因为你必须一遍又一遍地将所有元素移动到一个地方。列表可以在单个操作中执行此操作。当然,其他结构也有其自身的性能缺点,但这就是使用这些基本构建块设计复杂数据结构的美妙之处。

  2. 结构复杂性 - 您可以将向量同一行的列表视为有序容器,并可能将其扩展到可以在它们之上实现的多维矩阵,因为它们仍然保留一些基本的排序,但有更复杂的结构。以一棵树为例,一个简单的完整二叉树可以很容易地用向量实现,因为父子关系可以很容易地转换为索引算术,但是如果树不是满的并且每个节点有不同数量的子节点怎么办?现在,你可能会说它仍然可以完成(例如,任何图都可以通过邻接矩阵或邻接列表使用向量实现),但是当您可以使用指针链接进行更简单的实现时,这样做几乎没有意义。想想用数组做一个AVL滚动。:不寒而栗:

请注意,第二个论点很可能归结为性能("嘿,这是一个尴尬的方法,但我仍然设法使用向量!"),但它不止于此 - 它会使您的代码复杂化,使您的数据结构设计混乱,并可能使其更容易出现错误。

现在,"但是"来了 - 尽管使用该语言为您提供的所有可能工具很有意义,但使用基于矢量的结构来完成性能关键任务是非常广泛接受的。查看几乎所有科学的CPU基准测试,其中大多数最终依赖于向量(未引用,但如果有人感兴趣,我可以进一步详细说明。可以说,即使是著名的 *graph*500 也这样做了)。

原因不是它是最好的编程实践,而是它更适合 CPU 内部结构,并从硬件中获得更多的"果汁"。这是由于空间局部性 - CPU 非常喜欢这一点,因为它允许内存单元并行访问(在数组中,您始终知道下一个元素在哪里,在列表中您必须等到当前元素被获取),并且还发出流/跨步预取以减少未来请求的延迟。我不能说这总是一个好的做法,当你运行一个图时,即使你使用数组实现,访问仍然非常不规则,但这仍然是一种非常普遍的做法。

总而言之,从字面上理解这个问题 - 他们中的大多数都可以,各种各样的(对于"大多数"的给定定义,好吗?),但如果意图是"为什么要教指针",我相信你可以看到,为了理解你的极限以及你可以和应该使用什么 - 你需要知道的不仅仅是数组甚至指针。一个好的程序员应该了解一切 - 操作系统设计,CPU设计等。除非你真正了解你正在运行的结构,否则你不能做任何像样的事情,不幸的是(或不)包含很多指针。

可以使用std::vector作为后备存储来实现一种分配器。如果你这样做,基础计算机科学的所有标准数据结构都可以在向量之上实现。但是,它几乎不会让您摆脱使用指针:向量实际上只是内存块,带有一些有用的附加操作,最明显的是扩展能力。

更重要的是:如果你不理解指针,你也不会明白如何将vector用于高级数据结构。 vector是一个有用的抽象,但它遵循C++规则,即"你没有得到你不付钱的东西",所以它也是一个非常"薄"的抽象,你确实为抽象的成本付出了你必须编写的代码量。

(Jonathan Wakely在评论中指出,当你在分配器数据结构之上实现它们时,你不会得到C++标准库对分配器数据结构要求的确切保证vector。原则上,向量只是处理内存块的一种方式。

如果你正在学习C++你需要熟悉指针以及如何使用它们,即使有更高级的概念为你完成这项工作。是的,可以使用向量或列表实现大多数数据结构,如果您刚刚开始学习编程,那么您可能知道如何自己编写这些数据结构可能是个好主意。

话虽如此,生产代码应始终使用标准库,除非有充分的理由不这样做。