2015-01-20 3 views
6

here Я прочитал от принятого ответа, что станд :: Deque имеет следующие характеристикиКак Deque имеют амортизируются постоянное время Сложность

1- Random access - constant O(1) 
2- Insertion or removal of elements at the end or beginning - amortized constant O(1) 
3- Insertion or removal of elements - linear O(n) 

Мой вопрос о пункте 2. Как может Deque иметь амортизируется постоянная вставка в конце или в начале?

Я понимаю, что a std::vector имеет амортизированную постоянную временную сложность для вставок в конце. Это связано с тем, что вектор является условным и представляет собой динамический массив. Поэтому, когда в конце памяти заканчивается push_back, он выделяет целый новый блок памяти, копирует существующие элементы из старого местоположения в новое место и затем удаляет элементы из старого местоположения. Эта операция, которую я понимаю, амортизируется постоянной. Как это относится к deque? Как вставки в верхней и нижней части дека амортизируются постоянными. У меня создалось впечатление, что он должен быть постоянным O (1). Я знаю, что дека состоит из кусков памяти.

+1

применяется тот же принцип, что и 'std :: vector'. Операция - «O (1)», в случаях, когда требуются новые выделения, когда ранее выделенные слоты заполнены. – njzk2

+0

@ njzk2 Вам не нужно ** копировать старые значения. Вы просто выделяете новый * фиксированный размер * кусок и связываете его в начале/конце.Это O (1); не амортизируется O (1) [при условии, что выделение * фиксированного * объема памяти равно O (1)]. Возможно, он использует амортизацию, чтобы позволить реализациям использовать один большой блок смежной памяти, однако это было бы не очень полезно на практике, за исключением случаев, когда 'deque' не сильно меняется, и вам действительно нужна непрерывная память для выполнения определенных очень эффективно. – Bakuriu

+0

@Bakuriu: ok. Тогда только новые распределения. Спасибо за разъяснение. – njzk2

ответ

9

Обычная реализация deque в основном представляет собой вектор указателей на узлы фиксированного размера.

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

Вектор указательной части - это то, что (немного) более интересно. Когда мы выделяем достаточно узлы фиксированного размера, чтобы вектор указателей был заполнен, нам нужно увеличить размер вектора. Например, std::vector, нам нужно скопировать его содержимое на вновь выделенный вектор, поэтому его рост должен следовать геометрической (а не арифметической) прогрессии. Это означает, что у нас больше указателей на копирование, мы делаем копирование все реже и реже, поэтому общее время, затрачиваемое на копирование указателей, остается постоянным.

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

+1

Но если я правильно читаю стандарт, 23.3.3.4/3 заявляет, что '... Вставка одного элемента либо в начале, либо в конце deque всегда занимает постоянное время ...' где слово «всегда» похоже, исключает даже геометрическое перераспределение. –

+0

@MarkB: Я думаю, что ваше чтение является разумным, но единственным способом, который я знаю, чтобы удовлетворить эти (и все другие) требования, было бы предварительно выделить векторную часть как самую большую, какую вы когда-либо поддерживали, поэтому вы никогда не вырасти. Это явно возможно, но я думаю, что большинство людей предпочли бы расти, даже за счет амортизации постоянной сложности. –

+0

Я, случается, согласен с вашей интерпретацией, но я хотел убедиться, что я не пропустил что-то совершенно очевидное. –

1

The (профанного) ответ лежит в containers.requirements.general, 23.2.1/2:

Все требования сложности в настоящем пункте изложены исключительно в по количеству операций на содержащиеся объекты.

Перераспределение массива указателей, следовательно, не покрывается гарантией сложности стандарта и может занимать сколь угодно долго. Как упоминалось ранее, он, вероятно, добавляет амортизированные постоянные накладные расходы для каждого вызова push_front()/push_back() (или эквивалентного модификатора) в любой «нормальной» реализации. Однако я бы не рекомендовал использовать deque в RT-критическом коде. Как правило, в сценарии RT вы не хотите иметь неограниченные очереди или стеки (которые в C++ по умолчанию используют deque в качестве базового контейнера) в любом случае, ни распределение памяти, которое может потерпеть неудачу, так что вы, скорее всего, будете использовать предварительно выделенное кольцо буфер (например, Boost's circular_buffer).

+0

'std :: vector >' с упаковкой, чтобы она выглядела так, будто она содержит 'T', таким образом, будет иметь операции с постоянной стоимостью почти для всего? – Yakk

+0

Этот конкретный пример недействителен, поскольку стандарт гарантирует, что вектор смежный, в 23.3.7.1/1: «Элементы вектора хранятся смежно, что означает, что если v является вектором , где T другой тип, отличный от bool, тогда он подчиняется идентификатору & v [n] == & v [0] + n для всех 0 <= n

+0

А? Я просто указывал, что вы написали контейнер, как я уже упоминал, он (по стандартным правилам) подсчитывает многие операции как неамортизированные O (1), включая такие, как «стереть половину контейнера», но не «сортировать контейнер". Да, это не будет «вектор», но это будет контейнер. Мне это показалось забавным. – Yakk