|
1 | | -## Двухсторонняя очередь |
| 1 | +## Двусторонняя очередь |
| 2 | + |
| 3 | +**Двусторонняя очередь** - абстрактный тип данных, в котором элементы можно добавлять и удалять как в начало, так и в |
| 4 | +конец. В python есть реализация двусторонней очереди "deque", которая является обобщением очереди и стека. Данная |
| 5 | +структура поддерживает эффективное извлечение элемента из любого конца за **O(1)**. Доступ к первому и последнему |
| 6 | +элементу осуществляется также за **O(1)**, но имеет временную сложность за **O(n)** для доступа к произвольному |
| 7 | +элементу. |
| 8 | + |
| 9 | +Ниже приведена временная сложность операций для двусторонней очереди: |
| 10 | + |
| 11 | +1. Добавление нового элемента справа/слева - **O(1)**. |
| 12 | +2. Удаление элемента справа/слева - **O(1)**. |
| 13 | +3. Расширение на m элементов справа/слева - **O(m)**. |
| 14 | +4. Вставка нового элемента в произвольную позицию - **O(n)**. |
| 15 | +5. Удаление элемента с заданным значением - **O(n)**. |
| 16 | + |
| 17 | +## Реализация на базе массива |
| 18 | + |
| 19 | +При использовании динамического массива в качестве основы при реализации двусторонней очереди мы свободно можем |
| 20 | +добавлять и удалять элементы из хвоста. Эти операции выполняются за константное время. Однако массив не предназначен для |
| 21 | +вставки и удаления из начала. Поэтому мы заранее создаем массив на определенное количество элементов и начинаем его |
| 22 | +заполнение из середины. При достижении левой границы нам следует создать новый массив большего размера и скопировать |
| 23 | +туда все элементы в середину. Перевыделение памяти и копирование туда старых значений ухудшает стабильность |
| 24 | +производительности операций вставки в оба конца. Поэтому в данном случае мы можем говорить только об амортизационной |
| 25 | +сложности операций. |
| 26 | + |
| 27 | + |
| 28 | + |
| 29 | +## Реализация на базе двусвязного списка |
| 30 | + |
| 31 | +Двустороннюю очередь очень просто и понятно реализовать на базе двусвязного списка. Основным минусом данного подхода |
| 32 | +является медленная операция выделения памяти для нового элемента. При работе с указателями также используется |
| 33 | +дополнительная память. Поэтому при хранении простых элементов(например, bool), перерасход может составить 200 и даже 300 |
| 34 | +процентов. |
| 35 | + |
| 36 | +## Реализация с использованием блоков |
| 37 | + |
| 38 | + |
| 39 | + |
| 40 | +Идея реализации двусторонней очереди с использованием блоков объединяет оба подхода, описанных в предыдущих двух |
| 41 | +пунктах. В CPython структура deque представлена в виде статических массивов фиксированной длины размера 64, объединенных |
| 42 | +в двусвязный список. Это ускоряет доступ к отдельным элементам очереди по индексу и гарантирует, что удаляемый или |
| 43 | +извлекаемый элемент не затрагивает другие блоки данных. Другим преимуществом является то, что такое хранение позволяет |
| 44 | +полностью отказаться от функции realloc(), которая изменяет размер выделенного блока памяти, что приводит к более |
| 45 | +предсказуемой производительности. Использование блоков значительно снижает потребление памяти по сравнению с |
| 46 | +классическим двусвязным списком, где на одну ячейку данных приходятся две указателя на следующий и предыдущий элементы. |
| 47 | +В добавок к этому, статические массивы увеличивают вероятность использования кэша центрального процессора для уменьшения |
| 48 | +среднего времени доступа к компьютерной памяти. |
| 49 | + |
| 50 | +Список блоков никогда не бывает пустым, поэтому ``d.leftblock``(самый левый блок) и ``d.rightblock``(самый правый блок) |
| 51 | +никогда не равны NULL. Первый элемент очереди определяется как ``d.leftblock[leftindex]``, а последний |
| 52 | +как ``d.rightblock[rightindex]``. |
| 53 | + |
| 54 | +Индексы ``d.leftindex`` и ``d.rightindex`` всегда находятся в диапазоне ``0 <= index < BLOCKLEN``, по умолчанию BLOCKLEN |
| 55 | +равен 64. |
| 56 | + |
| 57 | +Для поддержания симметричности операций вставки слева и справа точная взаимосвязь индексов такова: |
| 58 | + |
| 59 | +``` |
| 60 | +(d.leftindex + d.len - 1) % BLOCKLEN == d.rightindex |
| 61 | +``` |
| 62 | + |
| 63 | +Всякий раз, когда левый и правый блоки указывают на один и тот же массив (``d.leftblock == d.rightblock``), взаимосвязь |
| 64 | +становится следующей: |
| 65 | + |
| 66 | +``` |
| 67 | +d.leftindex + d.len - 1 == d.rightindex |
| 68 | +``` |
| 69 | + |
| 70 | +Однако, когда блоков более одного, ``d.leftindex`` и ``d.rightindex`` являются индексами в разных массивах. В этом |
| 71 | +случае любой из указателей может быть больше другого. |
| 72 | + |
| 73 | +Для пустой очереди справедливо (CENTER равен ``((BLOCKLEN - 1) / 2)``): |
| 74 | + |
| 75 | +1. ``d.len == 0``, длина очереди равна нулю |
| 76 | +1. ``d.leftblock == d.rightblock``, левый и правый блоки указывают на один и тот же массив |
| 77 | +1. ``d.leftindex == CENTER + 1``, левый индекс располагается в позиции 32 |
| 78 | +1. ``d.rightindex == CENTER``, правый индекс равен 31 |
| 79 | + |
| 80 | +Ниже представлены основные структуры двусторонней очереди на CPython: |
| 81 | + |
| 82 | +``` |
| 83 | +#define BLOCKLEN 64 |
| 84 | +#define CENTER ((BLOCKLEN - 1) / 2) |
| 85 | +
|
| 86 | +typedef struct BLOCK { |
| 87 | + struct BLOCK *leftlink; |
| 88 | + PyObject *data[BLOCKLEN]; |
| 89 | + struct BLOCK *rightlink; |
| 90 | +} block; |
| 91 | +
|
| 92 | +typedef struct { |
| 93 | + PyObject_VAR_HEAD |
| 94 | + block *leftblock; |
| 95 | + block *rightblock; |
| 96 | + Py_ssize_t leftindex; /* 0 <= leftindex < BLOCKLEN */ |
| 97 | + Py_ssize_t rightindex; /* 0 <= rightindex < BLOCKLEN */ |
| 98 | + size_t state; /* incremented whenever the indices move */ |
| 99 | + Py_ssize_t maxlen; /* maxlen is -1 for unbounded deques */ |
| 100 | + Py_ssize_t numfreeblocks; |
| 101 | + block *freeblocks[MAXFREEBLOCKS]; |
| 102 | + PyObject *weakreflist; |
| 103 | +} dequeobject; |
| 104 | +``` |
0 commit comments