Доступ к STL по индексу по индексу O (1)? - PullRequest
32 голосов
/ 19 февраля 2010

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

abc-> defghi-> jkl-> mnop

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

Обновление: или есть другая распространенная реализация для deque?

Ответы [ 4 ]

27 голосов
/ 22 февраля 2010

Я нашел эту реализацию deque из Wikipedia :

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

Полагаю, это отвечает на мой вопрос.

2 голосов
/ 21 июня 2018

Данные в 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. Как с произвольным доступом a deque реализовать

1.итератор (__deque_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

1 голос
/ 24 ноября 2016

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

0 голосов
/ 19 февраля 2010

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

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

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

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

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

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

...