【发布时间】:2020-01-07 22:03:56
【问题描述】:
我有两个列表,我目前正在使用std::list < size_t >。
我们称它们为列表 A 和列表 B。
假设列表 A 有元素 [1, 2, 3, 4],列表 B 有元素 [5, 6, 7, 8]。
我想连接两个列表。但是,在我的应用程序中,有八种连接方式。我将描述两种保持最小化的方法:
- 直接连接 A 和 B。所以列表 C = [1, 2, 3, 4, 5, 6, 7, 8]。
- 反转列表 A 和 B,然后连接。所以列表 C = [4, 3, 2, 1, 8, 7, 6, 5]。
对于第一种情况,我可以使用以下代码 (source: cppreference):
std::list <size_t> C;
C.splice(C.begin(), A);
C.splice(C.end(), B);
这提供了一个很好的常量时间实现。
对于第二种情况,我必须反转列表,我可以使用 reverse() 来达到目的 (source: cppreference):
A.reverse();
B.reverse();
C_reversed.splice(C_reversed.begin(), A);
C_reversed.splice(C_reversed.end(), B);
std::cout << "C_reversed: " << C_reversed << std::endl;
这很好用,但是它具有线性时间复杂度。有一个更好的方法吗?我通读了Stack Overflow: Why does std::list::reverse have O(n) complexity?,并了解到使用reverse() 无法完成。有没有其他方法可以在恒定时间内完成此任务?
我的后备选项是维护四个列表,A 和 B 各两个,以便我可以在恒定时间内连接。然而,这会导致额外的空间复杂度。示例如下:
std::list <size_t> A1 = {1, 2, 3, 4};
std::list <size_t> A1_reversed = {4, 3, 2, 1};
std::list <size_t> B1 = {5, 6, 7, 8};
std::list <size_t> B1_reversed = {8, 7, 6, 5};
std::list <size_t> C1;
C1.splice(C1.begin(), A1);
C1.splice(C1.end(), B1);
std::list <size_t> C1_reversed;
std::cout << "C1: "<< C1 <<std::endl;
C1_reversed.splice(C1_reversed.begin(), A1_reversed);
C1_reversed.splice(C1_reversed.end(), B1_reversed);
std::cout << "C1_reversed: "<< C1_reversed <<std::endl;
实验代码如下:
/** Concatenation of two lists **/
#include <iostream>
#include <list>
std::ostream& operator<<(std::ostream& ostr, const std::list< size_t >& list) {
for (auto &i : list) {
ostr << " " << i;
}
return ostr;
}
int main(int argc, char **argv) {
/** Initialize **/
std::list < size_t > A = {1, 2, 3, 4};
std::list < size_t > B = {5, 6, 7, 8};
/** A + B **/
std::list < size_t > C;
C.splice(C.begin(), A);
C.splice(C.end(), B);
std::cout << C << std::endl;
/** Reverse A + reverse B **/
A = {1, 2, 3, 4};
B = {5, 6, 7, 8};
std::list < size_t > C_reversed;
A.reverse();
B.reverse();
C_reversed.splice(C_reversed.begin(), A);
C_reversed.splice(C_reversed.end(), B);
std::cout << C_reversed << std::endl;
/** Maintaining four lists **/
std::list <size_t> A1 = {1, 2, 3, 4};
std::list <size_t> A1_reversed = {4, 3, 2, 1};
std::list <size_t> B1 = {5, 6, 7, 8};
std::list <size_t> B1_reversed = {8, 7, 6, 5};
std::list <size_t> C1;
C1.splice(C1.begin(), A1);
C1.splice(C1.end(), B1);
std::list <size_t> C1_reversed;
std::cout << "C1: "<< C1 <<std::endl;
C1_reversed.splice(C1_reversed.begin(), A1_reversed);
C1_reversed.splice(C1_reversed.end(), B1_reversed);
std::cout << "C1_reversed: "<< C1_reversed <<std::endl;
return 0;
}
【问题讨论】:
-
您可以使用
rbegin和rend向后迭代列表,从而避免实际的反转。 -
迭代会导致线性时间复杂度,而
splice()不取reverse_iterators,即rbegin和rend。 -
当你打印列表时,它已经是 O(n)... 你可以最小化反转的次数。如果列表很短,那真的没有多大关系。