为什么没有更多的迭代器随机访问?

时间:2017-10-08 02:37:11

标签: c++ stl iterator

我正在尝试更多地了解C ++中的STL迭代器。我理解不同的数据结构有不同的迭代器,但我不明白为什么有些迭代器不是RandomAccess。例如,为什么LinkedList迭代器不是随机访问迭代器?我知道LinkedList不是一个“随机访问结构”的perse,但是我们不能实现迭代器来给出随机访问结构的错觉吗?例如,LinkedList有一个双向迭代器,它不定义+或+ =运算符,但它定义了++运算符。我们不能只使用以下内容定义+和+ =运算符:

iterator operator+= (int steps) {
     for (int i = 0; i < steps; ++i) {
          this->operator++();
     }
}

在查看了RandomAccessIterator的要求后,我认为我们可以为LinkedList实现大部分(如果不是全部)这些函数,那么为什么不呢?我猜它是因为某些操作基本上具有O(N)时间复杂度,但我不确定这是否是关键问题。如果我们使用这种方法实现RandomAccessIterators,那么这会对使用带有STL算法的LinkedList产生什么影响呢?我们突然能够使用std :: sort函数对LinkedList进行排序吗?

3 个答案:

答案 0 :(得分:5)

  

我猜它是因为某些操作基本上具有O(N)时间复杂度,但我不确定这是否是关键问题。

是的,这正是关键问题。迭代器以指针为模型。有了指针,人们就有了一定的期望。其中一个期望是指针加法和减法是非常快的操作(具体地,O(1))。标准库的设计者决定尊重这些期望。因此,如果标准库迭代器无法在O(1)中执行加法和减法,则它不会实现这些操作,也不会被归类为随机访问迭代器。

请注意,使用递增和递减运算符(++--),性能要求稍微放松,并且有一些迭代器在O(log n)而不是O( 1)。这种妥协是必要的,因为如果你不能递增或递减迭代器,它就没用多少。

  

如果我们使用这种方法实现RandomAccessIterators,那么对于使用带有STL算法的LinkedList会产生什么后果?我们突然能够使用std :: sort函数对LinkedList进行排序吗?

是。但它将成为(至少)一个O(n ^ 2)算法,而不是标准所承诺的O(n log n)。

答案 1 :(得分:3)

迭代器类别不仅仅是可能的;他们也是关于什么是合理的。

任何前向迭代器都可以提前X次。但是ForwardIterator不包括+ =整数。这很重要,因为当提供没有显式提供此接口的迭代器时,它允许针对RandomAccessIterator要求编写的代码显式失败。通过这样做,这样的代码可以声明自己具有特定的性能特征。

例如std::sort是O(n log(n))。但它只能承诺,因为它需要随机访问迭代器。理论上,您可以使用任何BidirectionalIterator实现相同的std::sort算法,但对于非随机访问迭代器,您的性能会非常糟糕。如此糟糕,你应该对你的代码做一些激烈的事情,而不仅仅是接受性能损失。因此,std::sort完全禁止它。

或者换句话说,如果有人告诉你为BidirectionalIterator实现sort,你会选择一个与RandomAccessIterator不同的算法。

其他算法可以更灵活。他们可能使用随机访问迭代器实现更快的实现,但他们仍然使用相同的通用算​​法(理论上)。这就是std::advance之类的函数存在的原因;它们允许仅仅Forward / BidirectionalIterators具有与RandomAccessIterators相同的整数偏移行为。但是你正在使用它们,并且完全知道它对于非RandomAccessIterator来说将是O(n)。对于某些算法,这种性能差异是可以的。

答案 2 :(得分:2)

std::prevstd::next都允许您使用单个函数调用来推进非随机访问迭代器。

C ++标准的设计者不会暴露效率低下的+,而是使用&#34;否则会很糟糕。

{{1}使用O(n)std::sort的随机访问迭代器将花费+或更糟。 Quicksort 不是排序迭代器的有效方法。

同时,基于O(n^2lgn)std::merge的排序在前向迭代器上几乎没有效率。只需在范围内前进,就像在树结构中存储指向每个2次幂子范围的指针一样。

由于找到并排序了两个子范围的一对幂,std::inplace_merge它们。这导致它们被分类。

这样的事情:

std::inplace_merge

为了简洁起见,但可以轻松地在甚至中撰写。除非我犯了错误,否则需要双向迭代器,并且是O(n lg n)。比template<class It> struct range_t { It b = {}; It e = {}; It begin() const { return b; } It end() const { return e; } bool empty() const { return begin()==end(); } }; template<class It> struct merge_range_t:range_t<It> { std::size_t pow2 = 0; }; template<class It> void merge_sort( range_t<It> to_sort ) { std::vector< merge_range_t<It> > sorted; auto initial = to_sort; auto do_merge = [&]{ auto a = sorted.back(); sorted.pop_back(); auto b = sorted.back(); sorted.pop_back(); std::inplace_merge( a.begin(), a.end(), b.end() ); sorted.push_back( {{ a.begin(), b.end() }, a.pow+1} ); }; auto should_merge = [&]{ if (sorted.size() < 2) return false; return sorted.back().pow2 == sorted[sorted.size()-2].pow2); }; while (!to_sort.empty()) { // elements of size 1 are always sorted: sorted.push_back( { {to_sort.begin(), std::next(to_sort.begin())} } ); // do merges of match size as required: while(should_merge()) do_merge(); // first element no longer needs sorting: to_sort = {std::next(to_sort.begin()), to_sort.end()}; } // the remaining sorted regions are not matched in size, but we still // need to merge them: while (sorted.size() > 1) do_merge(); } 更大的常数因子。