Доступ к очереди STL по индексу равен O (1)?

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

abc->defghi->jkl->mnop

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

Обновление: или есть ли другая общая реализация для двухсторонней очереди?


person jasonline    schedule 19.02.2010    source источник


Ответы (4)


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

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

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

person jasonline    schedule 22.02.2010
comment
Индексирование реализовано на самом деле не объясняет, как оно работает. - person curiousguy; 28.05.2013

Данные в 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, в основном о двух частях:

  1. итератор

  2. Как получить произвольный доступ к deque реализации

1. итератор (__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;
}
iterator skip n steps / random access
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

person Jayhello    schedule 21.06.2018

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

person Matvey    schedule 24.11.2016

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

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

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

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

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

ПРИМЕЧАНИЕ, что стандарт требует большего, чем эти два пункта, перечисленные выше. Это также требует, чтобы ссылки на элементы оставались действительными между вставками в конец или начало двухсторонней очереди. Этого можно добиться, если кольцевой буфер содержит указатели на фактические элементы, а не на сами элементы. В любом случае, мой ответ просто демонстрирует идею о том, что несколько реализаций могут удовлетворять определенным требованиям.

person P Shved    schedule 19.02.2010
comment
В этом случае элементы располагаются рядом друг с другом... но мне интересно, что, если это не реализовано как круговой буфер. - person jasonline; 19.02.2010
comment
@jasonline, независимо от того, как он реализован, его поведение регулируется стандартным документом C++. Он содержит требования к быстрому произвольному доступу к очереди, поэтому реализация STL не может быть реализована так, как вы предложили. Я просто описал, как это можно реализовать для соответствия стандарту. - person P Shved; 19.02.2010
comment
@jasonline: кольцевой буфер не гарантирует непрерывность размещения элементов. Память, в которой они отсортированы, непрерывна, но если элементы начинаются с позиции 987 и заканчиваются на позиции 5 (а в кольцевом буфере есть место для 1000 элементов), то они не являются смежными. - person Thanatos; 13.06.2010
comment
Ты неправ. Его нельзя реализовать поверх std::vector, потому что std::deque НИКОГДА не делает недействительными ссылки на элементы. Но std::vector ДОЛЖЕН, когда нет свободного места. - person f0b0s; 17.07.2012
comment
Если deque реализован как кольцевой буфер, deque не реализован как кольцевой буфер. - person curiousguy; 28.05.2013
comment
Вы голосуете против тех же людей, которые проголосовали за Each chunk is a vector, and the queue (“map” in the graphic below) of chunks itself is also a vector. на stackoverflow.com/a/6292437/110118? :) - person mlvljr; 01.05.2016