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 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
в основном о двух части:
итератор
как получить случайный доступ a
deque
понять
1. итератор(__deque_iterator
)
основная проблема итератора-когда++, -- 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);
}
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
Если deque реализован как кольцевой буфер на std::vector
, который перераспределяется, когда он растет в размере, то доступ по индексу действительно O (1).
стандарт предоставляет доказательства того, что такая реализация означает, по крайней мере, она соответствует стандарту по сложности оценки. Пункты 23.2.1.3 / 4 и 23.2.1.3/5 требуют, чтобы
вставка в начало или в конец deque требует постоянного времени, в то время как вставка в середина требует линейного времени размера deque
при стирании элементов из deque он может вызывать столько операторов присваивания, сколько расстояние от стираемых элементов до конца deque.
и, конечно же, вы должны рассчитывать на стандартные требования, а не на свое собственное видение того, как могут быть реализованы контейнеры и алгоритмы.
Примечание что стандарт требует больше, чем эти два пункты, перечисленные выше. Это также требует, чтобы ссылки на элементы оставались действительными между вставками в задней или передней части deque. Это может быть выполнено, если кольцевой буфер содержит указатели на фактические элементы, а не сами элементы. Во всяком случае, мой ответ просто демонстрирует идею о том, что несколько реализаций могут удовлетворять определенным требованиям.