我正在尝试更多地了解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进行排序吗?
答案 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::prev
和std::next
都允许您使用单个函数调用来推进非随机访问迭代器。
C ++标准的设计者不会暴露效率低下的+
,而是使用&#34;否则会很糟糕。
{{1}使用O(n)std::sort
的随机访问迭代器将花费+
或更糟。 Quicksort 不是排序迭代器的有效方法。
同时,基于O(n^2lgn)
或std::merge
的排序在前向迭代器上几乎没有效率。只需在范围内前进,就像在树结构中存储指向每个2次幂子范围的指针一样。
由于找到并排序了两个子范围的一对幂,std::inplace_merge
它们。这导致它们被分类。
这样的事情:
std::inplace_merge
c++17为了简洁起见,但可以轻松地在c++11甚至c++14中撰写。除非我犯了错误,否则需要双向迭代器,并且是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();
}
更大的常数因子。