为什么std::list
C ++标准库中类的反向函数具有线性运行时?我认为对于双向链表,反向函数应该是O(1)。
反转双向链接列表应该只涉及切换头和尾指针。
Reverse
函数在O(1)中实现吗?
为什么std::list
C ++标准库中类的反向函数具有线性运行时?我认为对于双向链表,反向函数应该是O(1)。
反转双向链接列表应该只涉及切换头和尾指针。
Reverse
函数在O(1)中实现吗?
Answers:
假设reverse
是O(1)。(再次假设)可能有一个布尔列表成员,它指示链接列表的方向当前与创建列表的原始方向相同还是相反。
不幸的是,这将降低其他任何操作的性能(尽管不更改渐近运行时)。在每个操作中,都需要查询一个布尔值,以考虑是否遵循链接的“下一个”或“上一个”指针。
由于这大概被认为是相对少见的操作,因此该标准(该标准不规定实现,仅要求复杂性)规定该复杂度可以是线性的。这允许“下一个”指针始终明确地指示相同的方向,从而加快了常见情况的操作。
reverse
与O(1)
复杂性,而不会影响任何其他操作的大O,通过使用此布尔标志伎俩。但是,实际上,即使在技术上为O(1),在每个操作中都需要一个额外的分支,因此代价很高。相比之下,您无法创建一个列表结构,其中sort
O(1)并且所有其他操作的成本相同。问题的关键在于,表面上,O(1)
如果您只关心大O,就可以免费获得反向收益,那么为什么他们不这样做呢?
std::uintptr_t
。然后您可以对它们进行异或运算。)
std::uintptr_t
,您可以强制转换为char
数组,然后对组件进行XOR。它会比较慢,但100%可移植。可能您可以使它在这两种实现之间进行选择,并且在uintptr_t
缺少时仅将第二种用作后备。如果在此答案中有描述,则需要一些说明:stackoverflow.com/questions/14243971/…–
这可能是Ø(1)如果该列表将存储允许调换“的含义的标志prev
”和“ next
”指针每个节点都有。如果反转列表是经常的操作,那么实际上添加这样的列表可能会有用,并且我不知道任何理由为何当前标准禁止实施它。但是,拥有这样的标志会使列表的普通遍历更加昂贵(如果仅通过一个恒定的因素),因为
current = current->next;
在operator++
列表迭代器的中,您将获得
if (reversed)
current = current->prev;
else
current = current->next;
这不是您决定轻松添加的内容。考虑到列表遍历的次数通常比翻转遍历的次数多,因此标准强制采用此技术是非常不明智的。因此,允许反向运算具有线性复杂度。不过请注意,即牛逼 ∈ Ô(1)⇒ 牛逼 ∈ Ø(ñ),因此,如前所述,实现您“优化”技术将被允许。
如果您来自Java或类似的背景,您可能想知道为什么迭代器每次都要检查标志。难道我们不是有两个不同的迭代器类型,无论从公共基类派生,并有std::list::begin
与std::list::rbegin
多态返回相应的迭代器?可能的话,这会使整个情况变得更糟,因为推进迭代器现在将是间接(很难内联)的函数调用。在Java中,您还是要按常规方式支付此价格,但是再次重申,这是许多人在性能至关重要时使用C ++的原因之一。
正如本杰明·林德利(Benjamin Lindley)在评论中所指出的那样,由于reverse
不允许迭代器无效,因此标准允许的唯一方法似乎是将指向指针的指针存储在迭代器内部,这会导致双重间接内存访问。
std::list::reverse
不会使迭代器无效。
next
和prev
指针存储在数组中,并将方向存储为a 0
或1
。若要向前迭代,请遵循pointers[direction]
并进行反向迭代pointers[1-direction]
(反之亦然)。这仍然会增加一点点开销,但可能少于分支。
swap()
指定为恒定时间,并且不会使任何迭代器无效。
当然,因为所有支持双向迭代器的容器都具有rbegin()和rend()的概念,所以这个问题没有意义吗?
构建一个代理来反转迭代器并通过该代理访问容器很简单。
这种非运算的确是O(1)。
如:
#include <iostream>
#include <list>
#include <string>
#include <iterator>
template<class Container>
struct reverse_proxy
{
reverse_proxy(Container& c)
: _c(c)
{}
auto begin() { return std::make_reverse_iterator(std::end(_c)); }
auto end() { return std::make_reverse_iterator(std::begin(_c)); }
auto begin() const { return std::make_reverse_iterator(std::end(_c)); }
auto end() const { return std::make_reverse_iterator(std::begin(_c)); }
Container& _c;
};
template<class Container>
auto reversed(Container& c)
{
return reverse_proxy<Container>(c);
}
int main()
{
using namespace std;
list<string> l { "the", "cat", "sat", "on", "the", "mat" };
auto r = reversed(l);
copy(begin(r), end(r), ostream_iterator<string>(cout, "\n"));
return 0;
}
预期输出:
mat
the
on
sat
cat
the
鉴于此,在我看来,标准委员会没有花时间要求对容器进行O(1)逆序排序,因为这是没有必要的,并且标准库很大程度上是基于仅强制要求必需的内容而建立的,避免重复。
只是我的2c。