STL deque доступ по индексу O (1)?

Я читал, что доступ к элементам по индексу позиции может быть выполнен в постоянное время в STL deque. Насколько мне известно, элементы в deque могут храниться в нескольких несмежных местах, исключая безопасный доступ через арифметику указателя. Например:

abc - >defghi->jkl - >mnop

элементы deque выше состоит из одного символа. Набор символов в одной группе указывает, что он выделяется в непрерывной памяти (например, abc находится в одном блоке память, defhi находится в другом блоке памяти и т. д.). Может ли кто-нибудь объяснить, как доступ по индексу позиции может быть выполнен в постоянное время, особенно если элемент, к которому нужно получить доступ, находится во втором блоке? Или у deque есть указатель на группу блоков?

Update: или есть ли какая-либо другая общая реализация для deque?

4 ответов


Я нашел эту реализацию deque из Википедия:

хранение содержимого в нескольких меньших массивах, выделение дополнительных массивов в начале или конце по мере необходимости. Индексирование осуществляется путем сохранения динамического массива, содержащего указатели на каждый из меньших массивов.

думаю, это ответ на мой вопрос.


одной из возможных реализаций может быть динамический массив массивов const-размера; такой массив const-размера может быть добавлен в любой конец, когда требуется больше места. Все массивы заполнены, за исключением, возможно, первого и последнего, которые могут быть частично пустыми. Это означает, что зная размер каждого массива и количество использованных элементов в первом массиве можно легко найти позицию элемента index.
http://cpp-tip-of-the-day.blogspot.ru/2013/11/how-is-stddeque-implemented.html


данные в deque хранятся чунками вектора фиксированного размера, которые являются

pointered по 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. как получить случайный доступ a deque понять

1. итератор(__deque_iterator)

основная проблема итератора-когда++, -- iterator, он может перейти к другому чанку (если он указывает на край чанка). Например, существует три блока данных:chunk 1,chunk 2,chunk 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


Если deque реализован как кольцевой буфер на std::vector, который перераспределяется, когда он растет в размере, то доступ по индексу действительно O (1).

стандарт предоставляет доказательства того, что такая реализация означает, по крайней мере, она соответствует стандарту по сложности оценки. Пункты 23.2.1.3 / 4 и 23.2.1.3/5 требуют, чтобы

  • вставка в начало или в конец deque требует постоянного времени, в то время как вставка в середина требует линейного времени размера deque

  • при стирании элементов из deque он может вызывать столько операторов присваивания, сколько расстояние от стираемых элементов до конца deque.

и, конечно же, вы должны рассчитывать на стандартные требования, а не на свое собственное видение того, как могут быть реализованы контейнеры и алгоритмы.

Примечание что стандарт требует больше, чем эти два пункты, перечисленные выше. Это также требует, чтобы ссылки на элементы оставались действительными между вставками в задней или передней части deque. Это может быть выполнено, если кольцевой буфер содержит указатели на фактические элементы, а не сами элементы. Во всяком случае, мой ответ просто демонстрирует идею о том, что несколько реализаций могут удовлетворять определенным требованиям.