【发布时间】:2009-09-16 23:23:56
【问题描述】:
两者有什么区别?我的意思是方法都是一样的。因此,对于用户而言,它们的工作方式相同。
对吗??
【问题讨论】:
-
我对迭代性能很感兴趣。从开始到结束的迭代速度更快?
两者有什么区别?我的意思是方法都是一样的。因此,对于用户而言,它们的工作方式相同。
对吗??
【问题讨论】:
让我列出不同之处:
复杂性
Insert/erase at the beginning in middle at the end
Deque: Amortized constant Linear Amortized constant
List: Constant Constant Constant
【讨论】:
constant和amortized constant有什么区别?
来自deque的(过时但仍然非常有用的)SGI STL摘要:
双端队列很像向量:和向量一样,它是一个序列,支持随机访问元素,在序列末尾以恒定时间插入和移除元素,以及在序列中线性时间插入和移除元素中间。
deque 与 vector 的主要不同之处在于,deque 还支持在序列开头进行恒定时间的元素插入和删除。此外,deque 没有任何类似于 vector 的 capacity() 和 reserve() 的成员函数,并且不提供与这些成员函数相关的迭代器有效性的任何保证。
这是来自同一站点的list 的摘要:
列表是一个双向链表。也就是说,它是一个既支持向前遍历又支持向后遍历的序列,以及(摊销的)常数时间在开头或结尾或中间插入和移除元素。列表具有重要的属性,即插入和拼接不会使列表元素的迭代器无效,即使删除也会使指向被删除元素的迭代器无效。迭代器的顺序可能会改变(也就是说,list::iterator 在列表操作之后可能有不同的前任或后继者),但迭代器本身不会失效或指向不同的元素,除非失效或突变是明确的。
总的来说,容器可能具有共享例程,但这些例程的时间保证因容器而异。在考虑将这些容器中的哪一个用于某项任务时,这一点非常重要:考虑到如何该容器将最常使用(例如,更多用于搜索而不是插入/删除)有很长的路要走引导您到正确的容器。
【讨论】:
std::list 基本上是一个双向链表。
另一方面,std::deque 的实现更像std::vector。它具有恒定的索引访问时间,以及在开头和结尾的插入和删除,这提供了与列表截然不同的性能特征。
【讨论】:
另一个重要的保证是每个不同的容器在内存中存储数据的方式:
请注意,deque 旨在尝试平衡向量和列表的优点,而不存在各自的缺点。它是内存有限平台(例如微控制器)中特别有趣的容器。
内存存储策略经常被忽视,然而,它往往是为某个应用选择最合适的容器的最重要原因之一。
【讨论】:
没有。 deque 只支持前后的 O(1) 插入和删除。例如,它可以在带有环绕的向量中实现。由于它还保证 O(1) 随机访问,因此您可以确定它没有使用(仅)双向链表。
【讨论】:
我为 C++ 班的学生制作了插图。 这是(松散地)基于(我对)GCC STL实现中的实现( https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/stl_deque.h 和 https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/stl_list.h)
集合中的元素存储在内存块中。每个块的元素数量取决于元素的大小:元素越大,每个块越少。潜在的希望是,如果无论元素的类型如何,块的大小都相似,这在大多数情况下都会对分配器有所帮助。
您有一个列出内存块的数组(在 GCC 实现中称为映射)。所有内存块都已满,除了第一个可能在开头有空间和最后一个可能在结尾有空间。地图本身是从中心向外填充的。与std::vector 相反,这就是如何在恒定时间内完成两端的插入。与std:::vector 类似,可以在恒定时间内进行随机访问,但需要两次间接而不是一次。与std::vector 类似,与std::list 相反,在中间删除或插入元素的成本很高,因为您必须重新组织大部分数据结构。
双向链表可能更常见。每个元素都存储在自己的内存块中,独立于其他元素分配。在每个块中,都有元素的值和两个指针:一个指向前一个元素,一个指向下一个元素。它使在列表中的任何位置插入元素变得非常容易,甚至可以将元素的子链从一个列表移动到另一个列表(称为 splicing 的操作):您只需更新指针插入点的开始和结束。缺点是要通过索引找到一个元素,您必须遍历指针链,因此随机访问在列表中的元素数量上具有线性成本。
【讨论】:
deque 和 list 之间的显着差异
对于deque:
并排存放的物品;
针对从两侧(正面、背面)添加数据进行了优化;
按数字(整数)索引的元素。
可以被迭代器甚至元素的索引浏览。
访问数据的时间更快。
对于list
项目“随机”存储在内存中;
只能被迭代器浏览;
针对中间的插入和移除进行了优化。
由于空间局部性非常差,对数据的时间访问速度较慢,迭代速度较慢。
很好地处理大型元素
您还可以查看以下Link,它比较了两个 STL 容器(使用 std::vector)之间的性能
希望我分享了一些有用的信息。
【讨论】:
其他人已经很好地解释了性能差异。我只是想补充一点,类似甚至相同的接口在面向对象编程中很常见——这是编写面向对象软件的一般方法的一部分。你绝不应该仅仅因为两个类实现相同的接口就假设它们以相同的方式工作,就像你不应该假设一匹马像狗一样工作,因为它们都实现了attack()和make_noise()。
【讨论】:
这是一个使用列表的概念验证代码,无序映射提供 O(1) 查找和 O(1) 精确的 LRU 维护。需要(非擦除)迭代器才能在擦除操作中存活。计划在 O(1) 任意大的软件管理缓存中使用 GPU 内存上的 CPU 指针。向 Linux O(1) 调度程序致敬(LRU 每个处理器的运行队列)。 unordered_map 通过哈希表进行恒定时间访问。
#include <iostream>
#include <list>
#include <unordered_map>
using namespace std;
struct MapEntry {
list<uint64_t>::iterator LRU_entry;
uint64_t CpuPtr;
};
typedef unordered_map<uint64_t,MapEntry> Table;
typedef list<uint64_t> FIFO;
FIFO LRU; // LRU list at a given priority
Table DeviceBuffer; // Table of device buffers
void Print(void){
for (FIFO::iterator l = LRU.begin(); l != LRU.end(); l++) {
std::cout<< "LRU entry "<< *l << " : " ;
std::cout<< "Buffer entry "<< DeviceBuffer[*l].CpuPtr <<endl;
}
}
int main()
{
LRU.push_back(0);
LRU.push_back(1);
LRU.push_back(2);
LRU.push_back(3);
LRU.push_back(4);
for (FIFO::iterator i = LRU.begin(); i != LRU.end(); i++) {
MapEntry ME = { i, *i};
DeviceBuffer[*i] = ME;
}
std::cout<< "************ Initial set of CpuPtrs" <<endl;
Print();
{
// Suppose evict an entry - find it via "key - memory address uin64_t" and remove from
// cache "tag" table AND LRU list with O(1) operations
uint64_t key=2;
LRU.erase(DeviceBuffer[2].LRU_entry);
DeviceBuffer.erase(2);
}
std::cout<< "************ Remove item 2 " <<endl;
Print();
{
// Insert a new allocation in both tag table, and LRU ordering wiith O(1) operations
uint64_t key=9;
LRU.push_front(key);
MapEntry ME = { LRU.begin(), key };
DeviceBuffer[key]=ME;
}
std::cout<< "************ Add item 9 " <<endl;
Print();
std::cout << "Victim "<<LRU.back()<<endl;
}
【讨论】: