STL什么是随机访问和顺序访问?

时间:2014-06-10 17:44:49

标签: c++ stl random-access

所以我很想知道,什么是随机访问?

我搜索了一下,但找不到多少。我现在的理解是"阻止"在容器中随机放置(如here所示)。随机访问意味着我可以访问容器的每个块,无论在什么位置(所以我可以在不经过所有块之前读取它在第5个位置上所说的内容),而在顺序访问时,我必须经历第1个,第2个,第3和第4到达第5街区。

我是对的吗?或者如果没有,那么有人可以向我解释随机访问是什么和顺序访问是什么?

3 个答案:

答案 0 :(得分:7)

顺序访问意味着访问第5个元素的成本是访问第一个元素的成本的5倍,或者至少是与集合中元素位置相关的成本增加。这是因为要访问集合的第5个元素,必须先执行操作以查找第1,第2,第3和第4个元素,因此访问第5个元素需要5个操作。

随机访问意味着访问集合中的任何元素与集合中的任何其他元素具有相同的成本。查找集合的第5个元素仍然只是一个操作。

因此,访问随机访问数据结构中的随机元素将具有O(1)成本,而访问顺序数据结构中的随机元素将具有O(n / 2) - > 0。 O(n)成本。 n / 2来自于如果想要访问集合中的随机元素100次,那么该元素的平均位置将大约是该集合的一半。因此,对于一组n个元素,它们来自n / 2(其中大O符号可以近似为n)。

你可能会觉得很酷的东西:

Hashmaps是实现随机访问的数据结构的示例。值得注意的一件事是,在哈希映射中的哈希冲突中,两个冲突元素存储在哈希映射中该桶中的顺序链表中。这意味着如果你有一个哈希映射的100%冲突,你实际上最终会有顺序存储。

这是一个散列图的图像,说明了我所描述的内容:

Hashmap

这意味着哈希映射的最坏情况实际上是用于访问元素的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的第二次编辑

我想序言这是我如何理解顺序存储的优点/用例,但我并不确定我对顺序容器的好处的理解,因为我对上面的答案有所了解。所以,如果我误会,请纠正我。

顺序存储非常有用,因为数据实际上会按顺序存储在内存中。

实际上,您可以通过将指向该集合的前一个元素的指针偏移量来存储该类型的单个元素所需的字节数来实际访问顺序存储的数据集的下一个成员。

因为一个signed 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.

答案 1 :(得分:3)

这有两个主要方面,并且不清楚两者中的哪一个与您的问题更相关。其中一个方面是通过迭代器访问STL容器的内容,其中这些迭代器允许随机访问或前向(顺序)访问。另一个方面是以随机或顺序的顺序访问容器甚至只是内存本身。

迭代器 - 随机访问与顺序访问

要从迭代器开始,请举两个示例:std::vector<T>std::list<T>。向量存储值数组,而列表存储值的链接列表。前者按顺序存储在内存中,这允许任意随机访问:计算任何元素的位置与计算下一个元素的位置一样快。因此,顺序存储为您提供了有效的随机访问,迭代器为random access iterator

相比之下,列表为每个节点执行单独的分配,每个节点只知道其邻居的位置。因此,不能直接计算随机非邻居节点的位置。任何这样做的尝试都必须遍历所有中间节点,因此尝试跳过节点的算法可能会表现不佳。非顺序存储产生随机位置,因此仅产生有效的顺序访问。因此,列表提供的迭代器是bidirectional iterator,是几个不同的顺序迭代器之一。

内存 - 随机访问与顺序访问

然而,你的问题还有另一个问题。迭代器部分仅解决容器的遍历问题。但是,在此之下,CPU将以特定模式访问内存本身。虽然在高级别上CPU能够解决任何随机地址而没有计算它的位置的开销(它就像一个大向量),但在实践中,读取内存涉及缓存和许多细微之处,使得访问不同的部分记忆需要不同的时间。

例如,一旦您开始使用相当大的数据集,即使您正在使用向量,按顺序访问所有元素比访问某些元素更有效随机顺序。相比之下,列表并不能实现这一点。由于列表的节点甚至不一定位于顺序存储器位置,因此即使对列表项的顺序访问也不能顺序读取存储器,并且因此可能更昂贵。

答案 2 :(得分:2)

这些术语本身并不意味着任何性能特征,如@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;
}