为什么存储分配器使用循环链表来存储分配/空闲地址而不是平衡树?遍历链表需要O(n)的复杂度,而平衡的树可以遍历O(logn),对吗?它背后的优势/推理是什么?
答案 0 :(得分:5)
前提(“存储分配器使用循环链表来存储分配/空闲地址”)不一定正确。对于某些分配器来说可能是这样,但总的来说并非如此。
如果分配器使用类似链表的结构来跟踪内存块,它通常作为元数据嵌入内存块本身 - 即。不是作为一个单独的数据结构。
例如,每个内存块都可以以状态(空闲/已分配)和块的大小开始。这种方法基本上实现了一个链表(使用大小,你可以很容易地确定下一个块的起始地址),但它有链接列表没有的其他属性:你仍然可以找到一个特定的内存块(节点)通过了解其内存地址。
因此,您有一个O(1)访问时间(因为您或编译器知道内存块的内存地址)。合并相邻的免费区块也很简单。如果需要运行某种去碎片或压缩算法,可以使用类似链表的结构来完成。找到足够大小的空闲块也可以使用类似链表的结构来完成(尽管有时第二个嵌入式链表特别用于空闲块,以最小化分配函数的开销)。
当然,这只是解决问题的一种方法。但它表明,使用链表并不一定是比另一种数据结构更糟糕的选择。
答案 1 :(得分:1)
嗯,分配器通常是专门建造的,非常仔细,特别是根据他们期望服务的特殊要求而精心设计。
因此,许多工业强度分配器中可能存在更复杂和更不规则的结构。
尽管如此,假设你的问题的前提是准确的:
最坏的情况复杂性与非常大的遍历最相关。大多数分配器的设计使得必要的遍历量通常非常小,以至于维持平衡树所需的额外开销使得遍历平均情况下的遍历变慢。此外,工程师更喜欢最简单的解决方案,除非更复杂的解决方案显然更好:循环链接列表很简单。
答案 2 :(得分:0)
遍历链表需要O(n)复杂度顺序
是的,但存储分配器的目的是提供一些分配的空间,并且这不一定需要“遍历”存储先前分配的结构。例如,如果我们每次都以特定大小的块分配内存(所以我们在结构中保留那个大小的块),那么我们只需要返回第一个。一般来说,我们只需找到一个足够大的节点,所以我们一直看,直到找到一个足够大的节点(这通常很快就会发生)。
而平衡树可以在O(logn)中遍历,对吧?
我们可以在O(logn)中找到一个特定元素,但我们不能在那个时间“遍历”树,因为根据定义,数据结构的“遍历”意味着访问每个节点,并且有O( n)节点。如果树具有适当的搜索树属性,我们只能“找到O(logn)中的特定元素。我们又想要哪个节点?这将让我们有效地找到,例如,足够大的最小分配;但这不一定是我们想要回馈的东西,无论如何(因为这个政策导致制作许多可能或可能不适合未来任何分配的小块,并且使结构膨胀)。See also