我已经读过按位置索引访问元素可以在STL双端队列中以恒定时间完成。据我所知,双端队列中的元素可能存储在几个非连续的位置,从而消除了通过指针算法的安全访问。例如:
ABC-> defghi-> jkl-> MNOP
上面的双端队列元素由一个字符组成。一组中的字符集表示它被分配在连续的存储器中(例如,abc在单个存储器块中,defhi位于另一个存储器块中,等等)。任何人都可以解释如何通过位置索引进行访问是如何在恒定时间内完成的,特别是如果要访问的元素在第二个块中?或者双端队列是否有指向这组块的指针?
更新:或者deque还有其他常见的实现吗?
答案 0 :(得分:27)
答案 1 :(得分:2)
deque
中的数据通过固定大小的矢量块存储,即
由map
指针(这也是向量的一部分,但其大小可能会改变)
deque iterator
的主要代码如下:
/*
buff_size is the length of the chunk
*/
template <class T, size_t buff_size>
struct __deque_iterator{
typedef __deque_iterator<T, buff_size> iterator;
typedef T** map_pointer;
// pointer to the chunk
T* cur;
T* first; // the begin of the chunk
T* last; // the end of the chunk
//because the pointer may skip to other chunk
//so this pointer to the map
map_pointer node; // pointer to the map
}
deque
的主要代码如下:
/*
buff_size is the length of the chunk
*/
template<typename T, size_t buff_size = 0>
class deque{
public:
typedef T value_type;
typedef T& reference;
typedef T* pointer;
typedef __deque_iterator<T, buff_size> iterator;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
protected:
typedef pointer* map_pointer;
// allocate memory for the chunk
typedef allocator<value_type> dataAllocator;
// allocate memory for map
typedef allocator<pointer> mapAllocator;
private:
//data members
iterator start;
iterator finish;
map_pointer map;
size_type map_size;
}
下面,我将为您提供deque
的核心代码,主要包括两部分:
迭代器
如何随机访问deque
实现
__deque_iterator
)迭代器的主要问题是,在++时,-迭代器可能会跳到其他块(如果它指向块边缘的指针)。例如,有三个数据块:chunk 1
,chunk 2
,chunk 3
。
pointer1
指向chunk 2
的开头,当操作符--pointer
时,它将指向chunk 1
的结尾,从而指向pointer2
。
下面,我将提供__deque_iterator
的主要功能:
首先,跳到任何块:
void set_node(map_pointer new_node){
node = new_node;
first = *new_node;
last = first + chunk_size();
}
请注意,用于计算块大小的chunk_size()
函数,为简化起见,您可以认为它返回8。
operator*
获取块中的数据
reference operator*()const{
return *cur;
}
operator++, --
//增量的前缀形式
self& operator++(){
++cur;
if (cur == last){ //if it reach the end of the chunk
set_node(node + 1);//skip to the next chunk
cur = first;
}
return *this;
}
// postfix forms of increment
self operator++(int){
self tmp = *this;
++*this;//invoke prefix ++
return tmp;
}
self& operator--(){
if(cur == first){ // if it pointer to the begin of the chunk
set_node(node - 1);//skip to the prev chunk
cur = last;
}
--cur;
return *this;
}
self operator--(int){
self tmp = *this;
--*this;
return tmp;
}
迭代器跳过n步/随机访问
self& operator+=(difference_type n){ // n can be postive or negative
difference_type offset = n + (cur - first);
if(offset >=0 && offset < difference_type(buffer_size())){
// in the same chunk
cur += n;
}else{//not in the same chunk
difference_type node_offset;
if (offset > 0){
node_offset = offset / difference_type(chunk_size());
}else{
node_offset = -((-offset - 1) / difference_type(chunk_size())) - 1 ;
}
// skip to the new chunk
set_node(node + node_offset);
// set new cur
cur = first + (offset - node_offset * chunk_size());
}
return *this;
}
// skip n steps
self operator+(difference_type n)const{
self tmp = *this;
return tmp+= n; //reuse operator +=
}
self& operator-=(difference_type n){
return *this += -n; //reuse operator +=
}
self operator-(difference_type n)const{
self tmp = *this;
return tmp -= n; //reuse operator +=
}
// random access (iterator can skip n steps)
// invoke operator + ,operator *
reference operator[](difference_type n)const{
return *(*this + n);
}
deque
元素 deque
的常用功能
iterator begin(){return start;}
iterator end(){return finish;}
reference front(){
//invoke __deque_iterator operator*
// return start's member *cur
return *start;
}
reference back(){
// cna't use *finish
iterator tmp = finish;
--tmp;
return *tmp; //return finish's *cur
}
reference operator[](size_type n){
//random access, use __deque_iterator operator[]
return start[n];
}
您还会看到这个问题,给出了deque
的主要代码
答案 2 :(得分:1)
可能的实现之一可以是const大小的数组的动态数组;当需要更多空间时,可以将这种const大小的数组添加到任一端。除了可能部分为空的第一个和最后一个数组之外,所有数组都是完整的。这意味着知道每个数组的大小以及第一个数组中使用的元素数量,可以通过索引轻松找到元素的位置。
http://cpp-tip-of-the-day.blogspot.ru/2013/11/how-is-stddeque-implemented.html
答案 3 :(得分:-3)
如果deque在std::vector
之上实现为ring buffer,它在大小增加时重新分配,那么按索引访问确实是O(1)。
该标准提供了证据表明这种实现意味着 - 至少它符合复杂性估计的标准。条款23.2.1.3/4和23.2.1.3/5要求
插入到双端队列的开头或末尾需要恒定的时间,而插入到中间需要线性时间的双端队列
当从双端队列中删除元素时,它可以调用尽可能多的赋值运算符,因为从被删除的元素到双端队列的末尾的距离是。
当然,您应该依靠标准要求而不是您自己对如何实施容器和算法的愿景。
注意标准要求超过上面列出的这两点。它还要求对元素的引用必须在对deque的背面或前面的插入之间保持有效。如果环形缓冲区包含指向实际元素的指针而不是元素本身,则可以满足这一要求。无论如何,我的回答只是证明了多个实现可以满足某些要求的想法。