STL 啥是随机访问和顺序访问?
Posted
技术标签:
【中文标题】STL 啥是随机访问和顺序访问?【英文标题】:STL What is Random access and Sequential access?STL 什么是随机访问和顺序访问? 【发布时间】:2014-06-10 17:44:48 【问题描述】:所以我很想知道,什么是随机访问?
我搜索了一下,找不到太多。我现在的理解是容器中的“块”是随机放置的(如here 所示)。随机访问意味着我可以访问容器的每个块,无论在什么位置(所以我可以阅读它在位置 5 上所说的内容,而无需在此之前遍历所有块),而对于顺序访问,我必须通过 1st , 2nd ,第 3 和第 4 到达第 5 块。
我说的对吗?或者如果不是,那么有人可以向我解释一下什么是随机访问和顺序访问?
【问题讨论】:
是的,您对它们的访问方式是正确的。但是我不确定您所说的 “容器中的“块”是随机放置的” 是什么意思。不管这意味着什么,听起来都不正确。 @BenjaminLindley 谢谢! 我不在乎教科书如何定义它,如果您上面的 def 是正确的,那么我猜 Iterator 实际上是顺序访问而不是随机访问。但这并不意味着数组不是随机访问。 【参考方案1】:顺序访问意味着访问第 5 个元素的成本是访问第一个元素的成本的 5 倍,或者至少与集合中的元素位置相关的成本增加。这是因为要访问集合的第 5 个元素,必须先进行一次运算,找到第 1、2、3、4 个元素,所以访问第 5 个元素需要 5 个运算。
随机访问意味着访问集合中的任何元素与集合中的任何其他元素具有相同的成本。找到集合的第 5 个元素仍然只是一个操作。
因此访问随机访问数据结构中的随机元素将花费 O(1),而访问顺序数据结构中的随机元素将花费 O(n/2) -> O( n) 成本。 n/2 来自于如果要访问集合中的随机元素 100 次,则该元素的平均位置将大约是集合的一半。因此,对于一组 n 个元素,结果为 n/2(在大 O 表示法中可以近似为 n)。
你可能会觉得很酷的东西:
哈希图是实现随机访问的数据结构的一个例子。需要注意的一件很酷的事情是,在哈希映射中的哈希冲突时,两个冲突的元素存储在哈希映射上该存储桶中的顺序链表中。所以这意味着如果你有 100% 的哈希映射冲突,你实际上最终会得到顺序存储。
这是一个哈希图的图像,说明了我所描述的内容:
这意味着哈希映射的最坏情况实际上是访问一个元素的 O(n),与顺序存储的平均情况相同,或者更准确地说,在哈希映射中找到一个元素是 Ω(n), O(1) 和 Θ(1)。其中Ω是最坏情况,Θ是最好情况,O是平均情况。
所以:
顺序访问:在 n 个元素的集合中找到一个随机元素是 Ω(n)、O(n/2) 和 Θ(1),对于非常大的数字,则变为 Ω(n )、O(n) 和 Θ(1)。
随机访问:在 n 个元素的集合中找到一个随机元素是 Ω(n/2)、O(1) 和 Θ(1),对于非常大的数字,则变为 Ω(n )、O(1) 和 Θ(1)
因此,随机访问的好处是可以为访问元素提供更好的性能,但顺序存储数据结构在其他方面也有好处。
@sumsar1812 的第二次编辑:
我想先说一下我是如何理解顺序存储的优点/用例的,但我对顺序容器的好处的理解不如我上面的答案那么确定。所以请在我错的地方纠正我。
顺序存储很有用,因为数据实际上会顺序存储在内存中。
您实际上可以通过将指向该集合的前一个元素的指针偏移存储该类型的单个元素所需的字节量来访问顺序存储数据集的下一个成员。
所以由于一个有符号的 int 需要 8 个字节来存储,如果你有一个固定的整数数组,并且有一个指向第一个整数的指针:
int someInts[5];
someInts[1] = 5;
someInts 是指向该数组的第一个元素的指针。向该指针添加 1 只会将其在内存中指向的位置偏移 8 个字节。
(someInts+1)* //returns 5
这意味着如果您需要以特定顺序访问数据结构中的每个元素,它会更快,因为每次查找顺序存储只是向指针添加一个常量值。
对于随机存取存储,不能保证每个元素都存储在其他元素附近。这意味着每次查找将比仅添加恒定数量更昂贵。
随机存取存储容器仍然可以通过使用迭代器来模拟看似有序的元素列表。但是,只要您允许对元素进行随机访问查找,就无法保证这些元素按顺序存储在内存中。这意味着即使一个容器可以表现出随机访问容器和顺序容器的行为,它也不会表现出顺序容器的好处。
因此,如果您的容器中元素的顺序应该是有意义的,或者您计划对数据集中的每个元素进行迭代和操作,那么您可能会从顺序容器中受益。
事实上,它仍然会变得更复杂一些,因为作为顺序容器的链表实际上并不按顺序存储在内存中,而作为另一个顺序容器的向量却可以。 Here's a good article that explains use cases for each specific container better than I can.
【讨论】:
将 O(1) -> O(n) 作为可能的解决方案不是更合适吗?对于顺序 当我说 O(n/2) -> O(n) 时,我的意思是当 n 趋于无穷大(或一些荒谬的大数)时,事实上你正在潜入它2 变得或多或少无关紧要,并且 O(n/2) 接近于 O(n)。我很抱歉,因为这有点模棱两可。 您能解释一下顺序存储带来好处的一些领域吗? @Sumsar1812 我用我的理解更新了我的答案。也就是说,我对那个答案的信心不如我对之前答案的那部分信心,所以我实际上可能在某些方面犯了错误。 您重新定义了大 O、大 Theta 和大 Omega 的含义,这让这个答案变得令人困惑。【参考方案2】:这有两个主要方面,目前尚不清楚两者中哪一个与您的问题更相关。其中一个方面是通过迭代器访问 STL 容器的内容,这些迭代器允许随机访问或前向(顺序)访问。另一方面是以随机或连续的顺序访问容器甚至只是内存本身。
迭代器 - 随机访问与顺序访问
从迭代器开始,举两个例子:std::vector<T>
和std::list<T>
。向量存储值的数组,而列表存储值的链表。前者按顺序存储在内存中,这允许任意随机访问:计算任何元素的位置与计算下一个元素的位置一样快。因此,顺序存储为您提供了高效的随机访问,而迭代器是 random access iterator。
相比之下,列表为每个节点执行单独的分配,每个节点只知道它的邻居在哪里。因此无法直接计算随机非邻居节点的位置。任何这样做的尝试都必须遍历所有中间节点,因此尝试跳过节点的算法可能会表现不佳。非顺序存储产生随机位置,因此只有有效的顺序访问。因此,list 提供的迭代器是bidirectional iterator,是几个不同的顺序迭代器之一。
内存 - 随机访问与顺序访问
但是,您的问题还有另一个问题。迭代器部分只处理容器的遍历。然而,在这之下,CPU 将以特定的模式访问内存本身。虽然在高级别的 CPU 能够寻址任何随机地址,而无需计算它在哪里的开销(它就像一个大向量),但实际上读取内存涉及缓存和许多微妙之处,这使得访问内存的不同部分需要不同的数量时间。
例如,一旦您开始使用相当大的数据集,即使您使用的是向量,按顺序访问所有元素也比按随机顺序访问所有元素更有效。相比之下,列表无法做到这一点。由于列表的节点甚至不一定位于顺序内存位置,因此即使对列表项的顺序访问也可能不会顺序读取内存,因此可能会更昂贵。
【讨论】:
我声明是为了验证我自己的理解,所以如果我错了,请纠正我。所以这就是访问固定大小数组中的元素比向量更有效的原因,因为由于固定大小数组的所有内存都是一次分配的,因此您可以保证数组的每个元素实际上是按顺序存储在内存中的?像向量这样的迭代它只是模拟顺序存储元素的行为(因为有些可能是在不同的时间分配的?) @echochamber 不,向量的分配等同于数组,因为如果向量必须调整其存储大小,它会重新分配并复制/移动所有元素,所以这些应该是等价的(除非你使用 @ 987654325@,或者有一个不擅长优化的编译器)。重要的区别在于向量和列表之间。 啊,有道理。感谢您的澄清。 @MichaelUrman:用迭代器听到“顺序访问”是非常罕见的,通常我们使用以下标签之一:“输入”“输出”“转发”“双向”或“随机访问”。 @MooingDuck 这很公平。将其表述为顺序遍历会更好吗?例如“[List 提供] 一个双向迭代器,它是提供顺序遍历的迭代器类型之一。”【参考方案3】:这些术语本身并不像@echochamber 所说的那样暗示任何性能特征。这些术语仅指访问方法。
“随机访问”是指以任意顺序访问容器中的元素。 std::vector
是一个 C++ 容器示例,它非常适合随机访问。 std::stack
是一个 C++ 容器示例,它甚至不允许随机访问。
“顺序访问”是指按顺序访问元素。这仅与订购的容器有关。某些容器针对顺序访问进行了比随机访问更好的优化,例如std::list
。
这里有一些代码来显示差异:
// random access. picking elements out, regardless of ordering or sequencing.
// The important factor is that we are selecting items by some kind of
// index.
auto a = some_container[25];
auto b = some_container[1];
auto c = some_container["hi"];
// sequential access. Here, there is no need for an index.
// This implies that the container has a concept of ordering, where an
// element has neighbors
for(container_type::iterator it = some_container.begin();
it != some_container.end();
++ it)
auto d = *it;
【讨论】:
不,我说这些条款本身并不意味着性能。简单地说“这个容器支持随机访问”并不意味着它的性能行为。 我想得越多,你是对的,但很难想到任何 O(n) 可以称为随机访问,但我已经见过 O (log(n)) 随机访问,所以你的观点是正确的。 需要注意的是,sequential可以指存储和访问;例如std::vector 是具有随机访问的顺序存储容器,而 std::list 是具有顺序访问的顺序存储容器。以上是关于STL 啥是随机访问和顺序访问?的主要内容,如果未能解决你的问题,请参考以下文章