【问题标题】:No O(1) operation to join elements from two forward_lists?没有 O(1) 操作来连接来自两个 forward_lists 的元素?
【发布时间】:2011-10-10 14:48:48
【问题描述】:

在阅读 C++11 的 FCD 中的 forward_listN2543 时,我偶然发现了 splice_after 的一个特定重载(稍微简化,让 cit 成为 const_iterator):

void splice_after(cit pos, forward_list<T>& x, cit first, cit last);

行为是在pos 之后(first,last) 之间的所有内容都移动到this。因此:

  this: 1 2 3 4 5 6           x: 11 12 13 14 15 16
          ^pos                      ^first   ^last
will become:
  this: 1 2 13 14 3 4 5 6     x: 11 12       15 16
          ^pos                      ^first   ^last

描述包括复杂性:

复杂度:O(距离(first, last))

我可以看到这是因为需要调整 PREDECESSOR(last).next = pos.next,而 forward_list 不允许这种情况在 O(1) 中发生。

好的,但是在 O(1) 中加入两个 单链表 不是这种简单数据结构的优势之一吗?因此我想知道 -- forward_list 上是否没有任何操作可以拼接/合并/加入 O(1) 中的任意数量的元素?

当然,算法非常简单。只需要一个操作名称(伪代码):(更新通过集成 Kerreks 答案)

temp_this  =   pos.next;
temp_that  =  last.next;
  pos.next = first.next;
 last.next =  temp_this;
first.next =  temp_that;

结果有点不同,因为移动的不是(first,last),而是(first,last]

  this: 1 2 3 4 5 6 7               x: 11 12 13 14 15 16 17
          ^pos                            ^first      ^last
will become:
  this: 1 2 13 14 15 16 3 4 5 6 7   x: 11 12             17
          ^pos       ^last                ^first   

我认为这是一个与前一个一样合理的操作,人们可能愿意这样做——尤其是如果它具有 O(1) 的好处。

  • 我是否忽略了对许多元素的 O(1) 运算?
  • 或者我的假设是错误的,即(first,last] 可能对移动范围有用?
  • 还是O(1)算法有错误?

【问题讨论】:

  • 您的解决方案的细节有点偏离,但问题很好。 +1
  • 因为不是 (first,last) 被移动,而是 (first,last])。 这正是关键所在,所有标准算法都使用该假设,但有一个例外会迷惑人的。
  • @PlasmaHH splice_after 不是已经违反了标准假设,因为所有其他算法都适用于 [first,last)?
  • 如果有before_end迭代器,那么确实可以做到。
  • @ChristianRau:确实。看看这有多混乱?还有另一个有偏差的版本会多么令人困惑。创建标准容器是为了易于使用,而不是为了最终性能。

标签: c++ complexity-theory c++11


【解决方案1】:

让我先给出一个你的 O(1) 拼接算法的修正版本,并举个例子:

temp_this  =   pos.next;
temp_that  =  last.next;

  pos.next = first.next;
 last.next =  temp_this;
first.next =  temp_that;

(健全性检查是观察每个变量精确出现两次,一次设置一次,一次获取。)

例子:

    pos.next                           last.next
    v                                  v
1 2 3 4 5 6 7        11 12 13 14 15 16 17 #    
  ^                     ^           ^     ^
  pos                   first       last  end             


becomes:

This:     1 2 13 14 15 16 3 4 5 6 7

That:   11 12             17

现在我们看到,为了拼接到that 列表的末尾,我们需要在end() 之前为提供一个迭代器。但是,在恒定时间内不存在这样的迭代器。所以基本上线性成本来自于以一种或另一种方式发现最终的迭代器:要么在 O(n) 时间内预先计算它并使用你的算法,要么你只是在线性时间内一个接一个地拼接。

(假设您可以实现自己的单链表,该列表将为before_end 存储一个额外的迭代器,您必须在相关操作期间保持更新。)

【讨论】:

  • 我们确实“结束前有一个”。是last.next,不是吗?
  • 我想我不够清楚。问题是,您提出的算法仅在可以明智地获得所需序列的最后一个元素的迭代器时才有用,但这通常在恒定时间内是不可能的。所以整个算法被认为没有用。换句话说,last 迭代器是怎么来的?
  • @towi:他的意思是last之前的一位。
  • 是的。从其他答案中得到。
  • @Dani:不,不是真的。我的意思是,一般来说,没有一个恒定时间的操作可以获得一个好的结束迭代器。想一想,对于已经通过其他方式获得迭代器对的情况,可能会提供此算法作为替代方案。这听起来像是对情境有用的东西。
【解决方案2】:

LWG 内部就这个问题进行了大量辩论。有关此问题的一些文档,请参阅 LWG 897

【讨论】:

    【解决方案3】:

    当您将end() 作为last 传递时,您的算法将失败,因为它会尝试使用单端节点并将其重新链接到另一个列表。允许end() 在除此之外的所有算法中使用是一个奇怪的例外。

    我还认为first.next = &amp;last; 必须是first.next = last.next;,否则last 将在两个列表中。

    【讨论】:

      猜你喜欢
      • 2018-11-11
      • 1970-01-01
      • 2017-10-13
      • 2012-06-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多