what is the point of saying that the complexity of insert() and erase() in the list container is constant time

huangapple go评论55阅读模式
英文:

what is the point of saying that the complexity of insert() and erase() in the list container is constant time

问题

我正在学习C++标准库,而在学习关于std::list的时候,我发现insert()erase()的复杂度是常数时间。

起初,我不知道这是如何可能的,直到我意识到我必须传递持有我想要插入位置的迭代器,所以函数本身是O(1)。

但是,我仍然需要花费O(N)的时间来遍历列表,将迭代器设置到我想要的位置。所以,说到底,它并不是O(1),而是O(N)。

那么,为什么每个人都说它是常数时间,当我们知道它不是?除非我们想要在前面或后面插入,或者除非我们有我们想要插入的迭代器,而这无法在O(N)的遍历之前知道,那么为什么说它比std::deque的插入和删除更快呢?我认为两者都需要O(N)的时间。

英文:

I'm learning the C++ standard library, and while learning about std::list I came across that the complexity of insert() and erase() is constant time.

Now, at first, I didn't know how that is possible, until I realized that I have to pass the iterator that holds the specified location where I want to insert into, so the function itself is O(1).

But, I still have to do O(N) time to traverse through the list to set the iterator to the position that I want. So, at the end of the day, its not O(1) but O(N).

Now, why does everyone say that its constant time when we know that its not? Unless we want to insert in the front or the back, or unless we have the iterator we want to insert into, which can't be known without an O(N) traverse, so why is it said that it's faster than std::deque's insert and erase? I think both need O(N) time.

答案1

得分: 1

std::list的删除确实是O(1)。

然而,正如您所指出的,最常见的情况是您没有迭代器,而是有一个索引或值。在这种情况下,您需要首先查找迭代器,而查找操作是O(n),正如您所指出的那样。

然而,在这种情况下,std::list速度较慢,您不应该在这种情况下使用它。在大多数情况下,您应该使用std::vectorstd::deque

在大多数情况下,唯一真正需要使用std::list的时间是作为辅助数据结构,其中主要数据结构包含对列表的迭代器。在这种情况下,删除确实是O(1)。

英文:

std::list deletion is indeed only O(1).

However, as you note, the most common scenario is that you don't already have an iterator, and instead have an index or value. In that case, you need to find the iterator first, and the find operation is O(n), as you note.

However, in this case, std::list is slow, and you shouldn't be using it in this case to start with. In most scenarios, you should be using std::vector or std::deque instead.

For the most part, the ONLY real time you should use std::list is as a secondary data structure, where the primary data structure contains iterators into the list. In that scenario, deletion is indeed only O(1).

答案2

得分: 1

为什么你要忽视这一点?FIFO(队列)和LIFO(栈)都很有用,经常被使用,并且都可以很好地与std::list(或任何其他链表)一起使用。

或者,除非我们拥有要插入的迭代器,而这个迭代器无法在没有O(n)遍历的情况下知道。

也许我们将迭代器存储在其他对象中,或者存储在像std::unordered_map这样具有常量查找时间的另一个容器中。这也是一个完全合理的用法。

你已经正确地识别出了一些不适合使用链表的情况。这并不意味着它从不适合使用,因为还有其他情况适合使用链表。

英文:

> now why everyone says that its constant time when we know that its not. unless we want to insert in the front or the back

Why are you discounting this? FIFO (queues) and LIFO (stacks) are both useful, and frequently used, and both work perfectly fine with std::list (or any other linked list).

> or unless we have the iterator we want to insert into which can't be known without a o(n) traverse

Maybe we stored the iterator in some other object, or another container like std::unordered_map which has constant lookup time. This is also a perfectly reasonable usage.

You've correctly identified some situations in which a list is not the right container. That doesn't mean it's never the right container, because there are other situations.

答案3

得分: 0

除非我们拥有要插入的迭代器,否则无法知道,而这需要进行O(N)的遍历。

错误。列表的一个巧妙特性之一是,在插入其他元素后,迭代器仍然有效。它们在删除元素后也保持有效,除非迭代器的元素被删除。在某些情况下,可以在不进行O(N)遍历的情况下知道迭代器。

考虑一个管理某种数据的类。代码的其他部分要求它存储数据,并作为回报,代码的这些部分接收到存储数据的“句柄”。稍后,可以使用该句柄来请求删除数据。假设我们选择的容器是std::list,因此句柄本质上可以是列表的迭代器。

假设我们不关心顺序,插入始终可以发生在列表的末尾。这是一个常量时间操作,即使考虑返回到添加的元素的迭代器。调用代码存储返回的句柄,这也是一个常量时间操作。

稍后,在列表中添加和删除了数千个其他元素后,迭代器仍然有效。将此迭代器/句柄还给管理类是一个常量时间操作,并且管理类从列表中删除数据也需要常量时间。无需搜索数据;迭代器保持有效。在这种情况下没有O(N)操作;一切都在常量时间内完成。


这个故事的教训是不要假设一个操作(例如遍历列表)是必要的,只是因为你没有看到或考虑到其他可能性。编写标准的人应该学到这个教训两次。

即使在所有已知情况下都需要遍历,标准也不应人为地强加需要遍历的假设。从链表中删除只需要常量时间,因此要强制执行这一点。你永远不知道未来会有什么聪明的主意。给予那个人尽可能好的工具(例如最小的删除复杂性),即使你自己不知道如何充分利用这些工具。

英文:

> unless we have the iterator we want to insert into, which can't be known without an O(N) traverse

False. One of the nifty features of a list is that iterators remain valid after inserting other elements. They also remain valid after deleting elements, unless the iterator's element is what was deleted. In some cases, the iterator can be known without an O(N) traverse.

Consider a class that manages data of some sort. Other parts of the code ask it to store data, and in return those parts of the code receive a "handle" to the stored data. Later, the handle could be used to request deletion of the data. Let's suppose the container of choice is a std::list, so the handles could be essentially iterators into the list.

Assuming we're not concerned about order, insertions can always occur at an end of the list. This is a constant-time operation, even after accounting for returning an iterator to the added element. The calling code stores the returned handle, also a constant-time operation.

Later, after potentially thousands of other elements have been added to and removed from the list, the iterator is still valid. It is a constant-time operation to give this iterator/handle back to the manager class, and it takes constant time for the manager to delete the data from the list. There is no need to search for the data; the iterator remained valid. There is no O(N) operation in this scenario; everything is done in constant time.


The moral of this story is do not assume that an operation (e.g. traversal of the list) is necessary simply because you have not seen—or thought of—other possibilities. People writing standards should learn this lesson twice.

Even if a traversal was needed in all known cases, the standard should not artificially impose the assumption that a traversal is needed. Deletion from a linked list requires only constant time, so enforce that. You never know what clever ideas someone will come up with in the future. Give that person the best tools possible (e.g. minimal complexity for deletion), even if you yourself do not know how to utilize those tools to their full potential.

huangapple
  • 本文由 发表于 2023年7月11日 02:30:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/76656397.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定