【问题标题】:How does Javascript's engine design affect user data structure implementations on worse case? [closed]Javascript 的引擎设计在更坏的情况下如何影响用户数据结构的实现? [关闭]
【发布时间】:2021-10-17 11:57:00
【问题描述】:

以一个使用哈希表实现关联数组(对象)的 JS Web 引擎为例。但正如我们所知,哈希表的情况更糟 O(n),因为冲突是不可避免的。

假设我开始使用 Javascript 开发一种新的数据结构,这种数据结构例如 LinkedList,插入/删除的情况更糟 O(1)。但是因为我用对象/数组来实现它。那么我的实现也至少是最坏的情况 O(n) 一定是真的。

我知道这个引擎优化得非常好,一个好的哈希函数平均会产生 O(1)。但是,我只是想确认我的认识,这并不像教科书所说的那么简单。是吗?

我想在所有数据结构的根部都用数组实现,因为访问总是 O(1),那么不应该用没有中间结构的数组来构建所有数据结构吗?另外,动态数组仍然有 delete O(n) 不能像我之前的例子一样可以滴下的问题吗?

这就是使用低级编程语言的好处比使用高级语言更好的地方吗?这么低的层次没有那么多抽象和教科书的复杂度数字居然能比得上?

如果我的想法到处都是,请道歉。

【问题讨论】:

  • 请澄清段落“我想在所有数据结构的根部都使用数组实现,[…]”。传统的数组访问是不变的,因为每个元素都具有相同的大小,因此,给定一个索引,您只需要查找 index · itemSize 的内存位置。但是,当然,JavaScript“数组”不是传统的数组。它们更接近散列图,已被现代引擎优化。与数组相比,hashmap 查找在算术上的方式不同(“green”·itemSize 没有意义)。删除 hashmap 键是 O(1),因为没有发生重新索引。
  • 抱歉,这里的实际问题是什么?这似乎是对编译器的开放式讨论,因为它涉及数据结构的优化以及标准库中语言的灵活性。
  • 我认为您过于强调一个指标:时间复杂度。时间复杂度只是您可以用来推理算法的众多工具之一。这不是所有数据结构和语言决策的最终目标。 TC 定义了操作的可扩展性,但它没有说明在现实世界中的实际使用,其中恒定(和许多其他)因素很重要,而这正是语言设计者最终关心的领域。投票结束时过于宽泛——请将讨论范围缩小到一个带有具体用例的具体问题。
  • 即使在 TC 内部,如果最坏的情况很糟糕怎么办?这并不意味着它仍然不是解决问题的最佳方法。一些例子是快速排序、插入排序和单纯形。如果哈希表设计得很好以消除冲突,QS 中的枢轴选择得很好,并且插入排序在大多数排序的数组上运行等等,那么最坏情况的惩罚在实践中通常不是问题。哈希表必须设计得很糟糕才能达到 O(n) 访问。你可以(通常)信任那些研究这些语言的聪明人。不要过早优化。

标签: javascript object time-complexity computer-science implementation


【解决方案1】:

“使用哈希表的关联数组(对象)” -> Javascript 对象很复杂,不仅仅是“它是一个哈希映射”。我不知道确切的技术细节,但我认为在其中存储了一定数量的值后它们会更改为哈希映射,此外它们还存储用于其他算法(如 Object.keys)的元数据以自动对键进行排序在他们被拉出哈希映射之后。再说一次,我不知道技术细节,但我知道,这不是直截了当的。

“正如我们所知,哈希表的情况更糟 O(n),因为冲突是不可避免的”-> 这取决于您使用的是什么哈希,但更重要的是,即使冲突是不可避免的,仅仅声称“它是最坏的情况 O(n)" 并保留它,因为它是 O(n) 的概率以对数方式下降到 0,它一次又一次地发现碰撞的机会是极不可能的,所以虽然它可能会找到无法有效描述时间复杂度的碰撞。

“必须是真的,我的实现也至少是最坏的情况 O(n)”-> 不正确,你在谈论两个不同的事情。如果您构建一个链表,每个节点将使用堆引用连接到下一个节点,这与 javascript 对象无关。遍历整个链表将是 O(n),但这是因为必须遍历每个下一个节点,而不是因为任何对象或散列。

“插入/删除的最坏情况 O(1)”-> 仅当您具有对要插入/删除它的节点的引用时才适用,否则您必须在插入/之前搜索它删除。但这在 javascript 中完全一样。

“那么不应该使用没有中间结构的数组来构建所有数据结构” -> 我知道的大多数数据结构(如列表、堆栈、队列)都是在普通数组之上实现的。那些不是的(比如二叉树、字典/映射或链表)没有在数组上实现,因为它真的没有意义。例如,将对象引用与链表一起使用的全部意义在于,您可以直接插入/删除某些内容,当您特别尝试提前使用时,使用引擎盖下的数组只会破坏使用链表的全部意义对象引用。

“也动态数组仍然有删除 O(n) 不能像我之前的例子一样会出现同样的问题”-> 不一定是因为当你将东西包装在一个对象中并在里面使用一个内部数组时,你可以添加元数据、索引、哈希、存储在数组外部的东西(对象私有)以及各种其他东西,以加速和跟踪该数组上的东西。因此,内部使用的复杂性不会自动溢出到在另一个对象中使用它。但是您确实需要小心,例如,如果您使用列表,那么它在 C# 等语言中的内部工作原理是,当您尝试在其满后向其添加更多元素时,它会使内部数组加倍,这可能会导致大量内存浪费。

据说 99% 的 javascript 用例不是“再优化 10 毫秒”,使用 javascript 是因为它的非 IO 阻塞特性、流式传输、异步/等待响应式编程以及它的快速开发速度,它也有 90% 用于网络通信,而不是一些高度优化的图形引擎。因此,在极少数情况下,您需要超过 9000 次复杂性优化、功能开发、代码可读性、可维护性,一般来说,类似的事情在 JS 中要大得多。除此之外,在大多数用例中,您不会使用 JS 从您的数据库中请求 1m 条数据记录,通常像您希望在页面上显示的 50 条数据记录,并且您将使用数据库来优化您的查询,几乎没有在任何 JS 开发(或一般的 Web 开发)中都需要使用如此大的数据结构。提取您需要的内容并请求更多或持续将您需要的内容传输给客户端要好得多。所以很多数据结构(比如二叉树)与 JS 中的东西并不真正相关,除非它是一个非常具体的用例。

【讨论】:

    【解决方案2】:

    但由于我使用对象/数组实现 [我的自定义数据结构]。那么我的实现也至少是最坏的情况 O(n) 一定是真的。

    没有。您的链接列表不会在任何地方使用带有 n 键的对象。你会有一个

    const linkedListNode = {
       value: …,
       next: null,
    };
    

    但即使这个使用具有O(n) 最坏情况成员访问权限的哈希表实现的,在您的情况下为n=2。您的对象中没有任意多个属性,只有两个。这就是您返回O(1) 的方式。

    这就是使用低级编程语言的好处比使用高级语言更好的地方吗?这么低的层次没有那么多抽象和教科书的复杂度数字居然能比得上?

    没有。即使在较低级别的编程语言中,您也可以开始质疑底层抽象。您认为在 C 中,内存中的索引数组访问是常数时间吗?不,因为页面错误和其他缓存恶作剧开始发挥作用。

    这就是为什么教科书的复杂性总是用machine model 来定义的。只要您将 JavaScript 执行模型中的对象属性访问定义为常量时间(这是一个非常合理的假设!它与现实世界非常相似),您的数字也确实适用于 JavaScript 代码。当然,您可以尝试解开抽象并根据较低级别的原语分析您的高级算法,但这样做没有意义。这正是我们首先拥有这些抽象的原因。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-12-29
      • 2021-11-18
      • 2023-03-15
      • 1970-01-01
      • 2021-02-23
      • 2010-11-22
      • 1970-01-01
      相关资源
      最近更新 更多