【发布时间】:2017-05-15 19:27:37
【问题描述】:
在学校我了解到链表由带有指向列表中下一个元素的指针的元素组成,而链表的缺点是查找大小具有线性复杂性,因为您必须遍历每个元素并计数.然而,我注意到在 C++11 中,std::list.size() 具有恒定的复杂性。这怎么可能?
【问题讨论】:
-
std::list将其大小存储在成员变量中。
标签: c++ c++11 linked-list
在学校我了解到链表由带有指向列表中下一个元素的指针的元素组成,而链表的缺点是查找大小具有线性复杂性,因为您必须遍历每个元素并计数.然而,我注意到在 C++11 中,std::list.size() 具有恒定的复杂性。这怎么可能?
【问题讨论】:
std::list 将其大小存储在成员变量中。
标签: c++ c++11 linked-list
很可能只保留一个成员变量,每当您添加或删除元素时,它就会更新。或者,如果您将两个列表合并在一起,只需将它们的计数相加即可。
那么你不必为了计算元素而遍历整个列表。
【讨论】:
这很容易。它缓存它。你可以缓存任何东西,只要你不让它变得无效。保持缓存有效有多难?1
为什么在 C++11 中?又是什么意思?
在 C++11 之前,std::list::size 没有提到它有多复杂。
有些实现保留了大小字段,有些则没有。保留大小字段的优点是.size() 是恒定时间; not 的好处是可以在恒定时间内完成一些拼接操作。
在跟踪size() 的情况下,拼接操作需要您计算正在拼接的节点数。这阻止了它成为常数时间,它变成了 O(n)。
C++11 决定,因为所有其他 .size() 函数都是常数时间,它们将要求 list 也具有常数时间大小。这破坏了一些 std::lists 的 ABI,并阻止了恒定时间拼接。
1 计算机编程有两个难题。命名事物、缓存失效和关闭一个错误。
【讨论】:
std::list 的二进制兼容性,因此针对 C++11 之前的 std::list 和符合 C++11 的代码编译的代码对于所有 T 都有不同的 sizeof(std::list<T>),因为额外的字段.
splice(pos, sourceList, begin, end) 期间遍历节点仅当我们想要更新正在操作的列表中的缓存大小值时才需要。我们只需要在size() 保证为 O(1) 的情况下这样做。