按索引访问的STL deque是O(1)?

时间:2010-02-19 14:54:28

标签: c++ stl deque random-access

我已经读过按位置索引访问元素可以在STL双端队列中以恒定时间完成。据我所知,双端队列中的元素可能存储在几个非连续的位置,从而消除了通过指针算法的安全访问。例如:

ABC-> defghi-> jkl-> MNOP

上面的双端队列元素由一个字符组成。一组中的字符集表示它被分配在连续的存储器中(例如,abc在单个存储器块中,defhi位于另一个存储器块中,等等)。任何人都可以解释如何通过位置索引进行访问是如何在恒定时间内完成的,特别是如果要访问的元素在第二个块中?或者双端队列是否有指向这组块的指针?

更新:或者deque还有其他常见的实现吗?

4 个答案:

答案 0 :(得分:27)

我从Wikipedia找到了这个双端队列实现:

将内容存储在多个较小的数组中,根据需要在开头或结尾分配其他数组。通过保持包含指向每个较小数组的指针的动态数组来实现索引。

我想这会回答我的问题。

答案 1 :(得分:2)

deque中的数据通过固定大小的矢量块存储,即

map指针(这也是向量的一部分,但其大小可能会改变)

deque internal structure

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的核心代码,主要包括两部分:

  1. 迭代器

  2. 如何随机访问deque实现

1。迭代器(__deque_iterator

迭代器的主要问题是,在++时,-迭代器可能会跳到其他块(如果它指向块边缘的指针)。例如,有三个数据块:chunk 1chunk 2chunk 3

pointer1指向chunk 2的开头,当操作符--pointer时,它将指向chunk 1的结尾,从而指向pointer2

enter image description here

下面,我将提供__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);
}

2。随机访问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的主要代码

https://stackoverflow.com/a/50959796/6329006

答案 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的背面或前面的插入之间保持有效。如果环形缓冲区包含指向实际元素的指针而不是元素本身,则可以满足这一要求。无论如何,我的回答只是证明了多个实现可以满足某些要求的想法。