std :: list :: splice和其他列表容器的复杂性

时间:2012-04-13 03:26:35

标签: c++ qt list std

我有一些代码处理各种std :: list对象,我目前正在使用一种非常低效的方法在它们之间传输内容(我正在迭代一个列表的任意部分,并逐个移动元素进入第二个清单)。我之前写过这段代码之前我才知道std :: list :: splice函数,我现在打算用我的代码替换它,例如:

list<string> list1, list2;

list1.push_back("a");
list1.push_back("b");
list1.push_back("c"); // list1: "a", "b", "c"

list<string>::iterator iterator1 = list1.begin();
iterator1++: // points to "b"

list2.push_back("x");
list2.push_back("y");
list2.push_back("z"); // list2: "x", "y", "z"

list<string>::iterator iterator2 = list2.begin();
iterator2++; // points to "y"

list1.splice(iterator1, list2, iterator2, list2.end());

// list1: "a", "y", "z", "b", "c"
// list2: "x"

我对拼接功能的复杂性有疑问。根据这个消息来源:

http://www.cplusplus.com/reference/stl/list/splice/

它应该在拼接的第一个和最后一个元素之间的范围内具有线性复杂度(在我的示例中为iterator2和list2.end()),并且源表明这是因为迭代器前进。我可以忍受这个,但我一直希望不断复杂。

我的假设(在我找到此源之前)是splice函数执行类似这样的操作:

  1. 切断“x”和“y”之间的链接
  2. 切断“z”和list2.end()
  3. 之间的链接
  4. 在“x”和list2.end()
  5. 之间形成一个链接
  6. 切断“a”和“b”
  7. 之间的链接
  8. 在“a”和“y”
  9. 之间形成一个链接
  10. 在“y”和“b”之间形成一个链接
  11. 因此,恢复两个列表以完成链。

    同样的原则将适用于任意大小的列表。我不确定我看到哪里需要splice函数来推进任何迭代器,因为我正在为它提供它所需要的所有迭代器。

    所以我的问题是,C ++规范如何解决这个问题?它是否仅在接头的起点和终点处断开并重新形成链路,还是逐个通过每个链路前进?如果是后者,那么任何其他列表容器(例如来自QT)是否提供前者?我的代码驻留在模拟程序的内部循环中,因此赋予它恒定而不是线性复杂性将是非常有价值的。

1 个答案:

答案 0 :(得分:30)

在C ++ 11的标准化过程中,这是一个非常有争议的话题。问题是所有标准容器(包括列表)也具有恒定时间size操作。

在C ++ 11之前,许多实现在不同列表之间的size线性时间和splice为常量时间。 C ++ 11现在要求size保持不变,splice为线性。

问题在于,如果拼接范围的长度不是逐个计算的,那么容器无法跟踪删除和添加的元素数量,以及下一次调用size需要在O(N)时间内恢复此信息 - 使用整个序列中较大的N,而不仅仅是拼接部分。

非标准list容器可以提供所需的操作,因为只要您不需要O(1)size,您的参数就是正确的。

至于其他图书馆......我不知道一个,但是Boost 应该有一个。 (我检查过,它没有,所以有人开始吧!)因为你清楚地了解如何自己编写,所以这样做可能比与Qt这样庞大的库竞争要少。

如果您不需要线程安全,可以在std::list周围实现一个包装器,其中每个容器只拥有一个单例列表的子范围。这将消除复制标准接口的大部分编程工作。