Простая реализация стеков и очередей с помощью Deque в Python

Первоначально опубликовано 6 августа 2022 года на сайте https://rivea0.github.io/blog.

Два абстрактных типа данных, с которыми вы, скорее всего, уже сталкивались, — это стеки и очереди. Важным аспектом является то, что каждый из них имеет различные принципы поведения при вставке и удалении элементов — LIFO (last in, first out) для стеков, FIFO (first in, first out) для очередей. При использовании стека последний вставленный элемент первым выходит, поэтому мы вставляем и вынимаем элементы с одного конца стека. В очереди первый вставленный элемент будет удален первым, подобно очереди в реальной жизни, поэтому операции enqueue и dequeue выполняются с противоположных концов очереди.

При использовании «двусторонней очереди», или deque (произносится как «колода»), мы можем зачислять и отчислять, а также выталкивать и выгружать элементы с обоих концов в любое время. Реализованная как двусвязный список, операции вставки и удаления занимают постоянное время O(1). Это еще одна причина, по которой deque отлично подходит — вы можете представить, что для той же цели мы можем использовать список Python list, но в этом случае, если мы хотим вставлять и удалять с начала (скажем, с левого конца), операция займет O(n) времени, что, ну, не очень хорошо.

Давайте посмотрим на это. Используя список, вы могли видеть стек, реализованный таким образом*:

class Stack:
    """Stack implementation as a list."""

    def __init__(self):
        """Create new stack."""
        self._items = []

    def is_empty(self):
        """Check if the stack is empty."""
        return not bool(self._items)

    def push(self, item):
        """Add an item to the stack."""
        self._items.append(item)

    def pop(self):
        """Remove an item from the stack."""
        return self._items.pop()

    def peek(self):
        """Get the value of the top item in the stack."""
        return self._items[-1]

    def size(self):
        """Get the number of items in the stack."""
        return len(self._items)
Войти в полноэкранный режим Выход из полноэкранного режима

И, например, очередь:

class Queue:
    """Queue implementation as a list."""

    def __init__(self):
        """Create new queue."""
        self._items = []

    def is_empty(self):
        """Check if the queue is empty."""
        return not bool(self._items)

    def enqueue(self, item):
        """Add an item to the queue."""
        self._items.insert(0, item)

    def dequeue(self):
        """Remove an item from the queue."""
        return self._items.pop()

    def size(self):
        """Get the number of items in the queue."""
        return len(self._items)
Вход в полноэкранный режим Выход из полноэкранного режима

Поскольку мы хотим использовать здесь deque вместо списка, давайте рассмотрим это на примере.

Мы можем инициализировать объект deque, передав в качестве аргумента итерабельную переменную. Он находится в модуле collections, поэтому мы также должны его импортировать:

from collections import deque

d = deque([7, 3, 0, 1])
print(d) # deque([7, 3, 0, 1])

empty_d = deque()
print(empty_d) # deque([])
Вход в полноэкранный режим Выйти из полноэкранного режима

Также помните, что строки — это последовательности, в таком случае наш deque будет выглядеть следующим образом:

d = deque('hey')
print(d) # deque(['h', 'e', 'y'])
Войти в полноэкранный режим Выход из полноэкранного режима

Мы также можем предоставить аргумент maxlen, чтобы указать максимальную длину элементов, которые мы хотим иметь в нашем deque — чтобы сделать его ограниченным.

Это тривиальный пример, но давайте разберемся, как это работает:

from collections import deque

d = deque([4, 5, 3, 1, 8], maxlen=3)
print(d) # deque([3, 1, 8], maxlen=3)

d = deque([4, 5, 3, 1, 8], maxlen=4)
print(d) # deque([5, 3, 1, 8], maxlen=4)
Войти в полноэкранный режим Выход из полноэкранного режима

Поскольку элементы итерабельной таблицы добавляются с одного конца, удаление других элементов (в случае примера maxlen=3, 4 и 5) будет происходить с противоположного конца.

Конечно, эффективность deque также обусловлена его методами appendleft() и popleft(), которые метко названы и лучше, чем list с точки зрения временной сложности.

from collections import deque

d = deque([7, 11])
d.appendleft(3)
print(d) # deque([3, 7, 11])

d.appendleft(1)
print(d) # deque([1, 3, 7, 11])

first_i = d.popleft()
print(first_i) # 1
print(d) # deque([3, 7, 11])
Вход в полноэкранный режим Выход из полноэкранного режима

У нас также есть методы append() и pop(), которые выполняют свои операции справа/справа — как обычный список:

from collections import deque

d = deque([2, 4, 6])
d.append(8)
print(d) # deque([2, 4, 6, 8])

first_popped = d.pop()
second_popped = d.pop()

print(f'Popped {first_popped} first, then {second_popped} second.')
# -> Popped 8 first, then 6 second.

print(d) # deque([2, 4])
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь, когда мы рассмотрели операции append и pop с обеих сторон, давайте сначала реализуем очередь, аналогичную версии list в начале статьи:

from collections import deque

class Queue:
    """Queue implementation as a deque."""

    def __init__(self):
        """Create new queue."""
        self._items = deque()

    def is_empty(self):
        """Check if the queue is empty."""
        return not bool(self._items)

    def enqueue(self, item):
        """Add an item to the queue."""
        self._items.append(item)

    def dequeue(self):
        """Remove an item from the queue."""
        return self._items.popleft()

    def size(self):
        """Get the number of items in the queue."""
        return len(self._items)
Вход в полноэкранный режим Выход из полноэкранного режима

Для стековой версии, поскольку нам нужно добавлять и выгружать с одного конца, методы append() и pop(), использующие list, сначала тоже могут показаться подходящими. Но давайте модифицируем предыдущую версию стека выше, чтобы реализовать его как deque:

from collections import deque

class Stack:
    """Stack implementation as a deque."""

    def __init__(self):
        """Create new stack."""
        self._items = deque()

    def is_empty(self):
        """Check if the stack is empty."""
        return not bool(self._items)

    def push(self, item):
        """Add an item to the stack."""
        self._items.append(item)

    def pop(self):
        """Remove an item from the stack."""
        return self._items.pop()

    def peek(self):
        """Get the value of the top item in the stack."""
        return self._items[-1]
Вход в полноэкранный режим Выйти из полноэкранного режима

Вроде бы ничего особенного не изменилось, но можно представить и другой конец, используя appendleft() вместе с popleft().

Мы рассмотрели очень простой способ создания стеков и очередей с помощью deque, но, конечно, есть еще много интересного. В первую очередь следует обратиться к официальной документации, также вы можете ознакомиться со статьей Real Python на эту тему. Как и во многих других случаях, вы сами решаете, чего хотите достичь, и двусторонняя очередь — это еще один инструмент в вашем наборе инструментов, который стоит рассмотреть.

* Примеры реализации стека и очереди в виде списка взяты из замечательной книги Брэда Миллера и Дэвида Ранума об алгоритмах и структурах данных.

Оцените статью
devanswers.ru
Добавить комментарий