这是我第一次正式地学习数据结构,对我来说,传统上描述的链表的一些好处(更容易的内存分配和更快的列表体的输入和删除)在js中似乎是没有意义的,因为数组是这样工作的(就像带数字键的对象)。有谁能给予个例子来说明为什么我想在javascript中使用链表?
kgsdhlau1#
正如注解所指出的,如果需要在列表中插入/删除常量时间,您可以这样做。Array有两种合理的实现方式,允许填充不连续的索引:1.作为一个实际的类似C的连续内存块,足够大以包含所有使用的索引;未填充的索引将包含一个对伪值的引用,因此它们不会被视为已填充的条目(并且超出max索引的多余容量可以被视为垃圾,因为length表示它不重要)1.作为以整数为键的哈希表(基于类似C的数组)在任何一种情况下,在末尾插入的成本都将被分摊O(1),但是每当底层类C数组的容量耗尽(或者超过哈希负载阈值)并且需要重新分配时,就会有O(n)的工作量。如果在开头或中间插入(并且所讨论的索引正在使用中),您可能会遇到与以前相同的工作高峰,以及新的问题。不,现有索引的数据不会更改。但是您强制输入的条目上方的所有键都必须递增如果它是一个普通的类似C的数组实现,那么它基本上就是一个memmove(对垃圾收集等的需求取模)如果是哈希表实现,则需要读取每个元素(从最高索引到最低索引,这意味着要么对键进行排序,要么查找当前长度以下的每个索引,即使它是一个稀疏的Array),弹出它,然后用一个高一的键值重新插入它。代价可能是巨大的,在一些浏览器中的实现可能会做一些聪明的无意义的事情,使用一个“偏移量”,内部使用相对于偏移量的负“键”来避免重散列,同时仍然在索引0之前插入,但这将使所有操作更昂贵,以换取使shift/unshift更便宜。关键是,用JavaScript编写的链表会因为是JS而产生开销(对于类似的工作量,它通常比内置的运行得慢),但是(忽略内存管理器本身引入工作高峰的可能性),这是可以预测的:
Array
length
O(1)
O(n)
memmove
0
shift
unshift
完全有可能所有的浏览器都使用偏移量来优化(避免memmove或在某些情况下重新键入,尽管它不能避免偶尔的realloc和memmove或重新散列而不浪费内存)我不知道主流浏览器是怎么做的,但如果你试图编写具有一致性能的可移植代码,您可能不想假设他们牺牲了一般性能,以使shift/unshift情况下使用大型Array更快,他们可能更喜欢使所有其他操作更快,并假设shift/X1 M23 N1 X将仅与成本微不足道的小X1 M24 N1 X一起使用。
realloc
f4t66c6m2#
我认为有一些合法的情况/理由更喜欢链表:
因此,如果你有一个链表,你可以在一个变量中存储一个对"currentItem"的引用,如果你需要访问该条目的邻居,你可以写:
curItem.getNext();
或
curItem.getPrev();
现在你可以说你可以对数组做同样的事情,而curItem只是当前的索引,基本上这是真的(在大多数情况下我会使用这一点),但是记住在javascript中可以跳过索引,所以如果你的数组看起来像这样,index-method就不会像想象的那么容易工作了:
myArray = []; myArray[10] = 'a'; myArray[20] = 'b';
如果你发现自己处于这种情况下,* 也许 * 一个链接是更好的选择。但是,如果您需要随机访问数据(这在大多数情况下并不常见),则几乎每次都可以使用数组。
如果你想把你的列表"拆分"成两个独立的列表,这也是可能的O(1)时间。对于数组,你需要使用slice,这是一个性能更差的操作。然而,只有当你处理大型数据集并且经常执行这个操作时,这才是一个问题。在我的机器上,对一个包含1000万个字符串的数组重复20次切片大约需要4秒。而将一个列表分离成两个列表所花的时间小于1秒(当然,前提是您已经引用了要开始分离的列表元素!)。
在某些情况下,你会从列表的本质和性能中获益,而在某些情况下,你会因为它的不性能而遭受损失(无法随机访问多个数据)我从来没有在JavaScript中使用过列表,但是类似的结构如树或图被用于数据表示(在后端和前端JavaScript中都是如此)。所以分析/学习JavaScript中的列表实现对于更复杂的结构来说是个好主意。
inn6fuwd3#
@noob-in-need我推荐你观看这个关于JavaScript垃圾收集器的视频:https://www.youtube.com/watch?v=RWmzxyMf2cE在书中,他解释了为什么使用链表可以给予你对代码的速度进行更细粒度的控制(正如ShadowRanger深入讨论的那样),还可以防止意外的垃圾收集速度减慢。
zd287kbt4#
这可以归结为array与linkedlist之间最基本的差异。1.在一个元素数组中插入一个新元素是非常昂贵的,因为必须为新元素创建空间,而要创建空间,需要移动现有的元素,但对于链表来说,这只是引用的改变。1.但是在数组中阅读和随机访问要比在链表中容易,随机访问是不允许的,我们必须从第一个节点开始顺序访问元素,所以我们不能用链表做二分查找。1.对于列表的每个元素,需要用于指针的额外存储空间,该指针用于存储next的引用。
array
linkedlist
next
kx1ctssn5#
下面是Linked List和Array之间的简单比较Ref: https://en.wikipedia.org/wiki/Linked_list#Disadvantages
Linked List
knpiaxh16#
令人惊讶的是,很少能找到链表优于构建在数组之上的数据结构的用例:1.阵列往往更适合缓存,并且添加到后端的平均速度更快(O(n)最差情况,但O(1)已摊销)1.一旦你需要搜索位置,链表的恒定时间优势福尔斯了,所以,如果你需要在列表中找到位置,你可以在O(1)中删除它,但是找到它已经是O(n)了,数组结构很可能会比链表更好。尽管如此,仍然存在使用链表及其恒定时间操作的场景。调度器是一个很好的例子,因为延迟很重要,并且保证O(1)成为一个因素。链表在Linux内核中使用,但由于您要求提供JavaScript示例,NodeJs运行时使用它们来实现计时器:
lib/internal/timer.js
internal/linkedlist.js
6条答案
按热度按时间kgsdhlau1#
正如注解所指出的,如果需要在列表中插入/删除常量时间,您可以这样做。
Array
有两种合理的实现方式,允许填充不连续的索引:1.作为一个实际的类似C的连续内存块,足够大以包含所有使用的索引;未填充的索引将包含一个对伪值的引用,因此它们不会被视为已填充的条目(并且超出max索引的多余容量可以被视为垃圾,因为
length
表示它不重要)1.作为以整数为键的哈希表(基于类似C的数组)
在任何一种情况下,在末尾插入的成本都将被分摊
O(1)
,但是每当底层类C数组的容量耗尽(或者超过哈希负载阈值)并且需要重新分配时,就会有O(n)
的工作量。如果在开头或中间插入(并且所讨论的索引正在使用中),您可能会遇到与以前相同的工作高峰,以及新的问题。不,现有索引的数据不会更改。但是您强制输入的条目上方的所有键都必须递增如果它是一个普通的类似C的数组实现,那么它基本上就是一个
memmove
(对垃圾收集等的需求取模)如果是哈希表实现,则需要读取每个元素(从最高索引到最低索引,这意味着要么对键进行排序,要么查找当前长度以下的每个索引,即使它是一个稀疏的Array
),弹出它,然后用一个高一的键值重新插入它。代价可能是巨大的,在一些浏览器中的实现可能会做一些聪明的无意义的事情,使用一个“偏移量”,内部使用相对于偏移量的负“键”来避免重散列,同时仍然在索引0
之前插入,但这将使所有操作更昂贵,以换取使shift
/unshift
更便宜。关键是,用JavaScript编写的链表会因为是JS而产生开销(对于类似的工作量,它通常比内置的运行得慢),但是(忽略内存管理器本身引入工作高峰的可能性),这是可以预测的:
shift
/unshift
(使用偏移量,shift
通常比较便宜,unshift
也比较便宜,除非unshift
-ed比shift
-ed多),那么在处理可能无限大的FIFO队列时,您肯定希望使用链表完全有可能所有的浏览器都使用偏移量来优化(避免
memmove
或在某些情况下重新键入,尽管它不能避免偶尔的realloc
和memmove
或重新散列而不浪费内存)我不知道主流浏览器是怎么做的,但如果你试图编写具有一致性能的可移植代码,您可能不想假设他们牺牲了一般性能,以使shift
/unshift
情况下使用大型Array
更快,他们可能更喜欢使所有其他操作更快,并假设shift
/X1 M23 N1 X将仅与成本微不足道的小X1 M24 N1 X一起使用。f4t66c6m2#
我认为有一些合法的情况/理由更喜欢链表:
因此,如果你有一个链表,你可以在一个变量中存储一个对"currentItem"的引用,如果你需要访问该条目的邻居,你可以写:
或
现在你可以说你可以对数组做同样的事情,而curItem只是当前的索引,基本上这是真的(在大多数情况下我会使用这一点),但是记住在javascript中可以跳过索引,所以如果你的数组看起来像这样,index-method就不会像想象的那么容易工作了:
如果你发现自己处于这种情况下,* 也许 * 一个链接是更好的选择。
但是,如果您需要随机访问数据(这在大多数情况下并不常见),则几乎每次都可以使用数组。
如果你想把你的列表"拆分"成两个独立的列表,这也是可能的O(1)时间。对于数组,你需要使用slice,这是一个性能更差的操作。然而,只有当你处理大型数据集并且经常执行这个操作时,这才是一个问题。在我的机器上,对一个包含1000万个字符串的数组重复20次切片大约需要4秒。而将一个列表分离成两个列表所花的时间小于1秒(当然,前提是您已经引用了要开始分离的列表元素!)。
在某些情况下,你会从列表的本质和性能中获益,而在某些情况下,你会因为它的不性能而遭受损失(无法随机访问多个数据)我从来没有在JavaScript中使用过列表,但是类似的结构如树或图被用于数据表示(在后端和前端JavaScript中都是如此)。所以分析/学习JavaScript中的列表实现对于更复杂的结构来说是个好主意。
inn6fuwd3#
@noob-in-need我推荐你观看这个关于JavaScript垃圾收集器的视频:https://www.youtube.com/watch?v=RWmzxyMf2cE
在书中,他解释了为什么使用链表可以给予你对代码的速度进行更细粒度的控制(正如ShadowRanger深入讨论的那样),还可以防止意外的垃圾收集速度减慢。
zd287kbt4#
这可以归结为
array
与linkedlist
之间最基本的差异。1.在一个元素数组中插入一个新元素是非常昂贵的,因为必须为新元素创建空间,而要创建空间,需要移动现有的元素,但对于链表来说,这只是引用的改变。
1.但是在数组中阅读和随机访问要比在链表中容易,随机访问是不允许的,我们必须从第一个节点开始顺序访问元素,所以我们不能用链表做二分查找。
1.对于列表的每个元素,需要用于指针的额外存储空间,该指针用于存储
next
的引用。kx1ctssn5#
下面是
Linked List
和Array
之间的简单比较Ref: https://en.wikipedia.org/wiki/Linked_list#Disadvantages
knpiaxh16#
令人惊讶的是,很少能找到链表优于构建在数组之上的数据结构的用例:
1.阵列往往更适合缓存,并且添加到后端的平均速度更快(
O(n)
最差情况,但O(1)
已摊销)1.一旦你需要搜索位置,链表的恒定时间优势福尔斯了,所以,如果你需要在列表中找到位置,你可以在
O(1)
中删除它,但是找到它已经是O(n)
了,数组结构很可能会比链表更好。尽管如此,仍然存在使用链表及其恒定时间操作的场景。调度器是一个很好的例子,因为延迟很重要,并且保证
O(1)
成为一个因素。链表在Linux内核中使用,但由于您要求提供JavaScript示例,NodeJs运行时使用它们来实现计时器:lib/internal/timer.js
internal/linkedlist.js