Files
hello-algo/ru/docs/chapter_stack_and_queue/deque.md
2026-01-23 18:13:14 +08:00

24 KiB
Raw Blame History

Двусторонняя очередь

В очереди можно удалять элементы только из начала и добавлять элементы только в конец. Как показано на рисунке ниже, двусторонняя очередь (double-ended queue) предоставляет большую гибкость, позволяя выполнять операции добавления или удаления элементов как в начале, так и в конце.

Операции двусторонней очереди

Основные операции с двусторонней очередью

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

Таблица   Эффективность операций двусторонней очереди

Метод Описание Временная сложность
push_first() Добавление элемента в начало очереди O(1)
push_last() Добавление элемента в конец очереди O(1)
pop_first() Удаление элемента из начала очереди O(1)
pop_last() Удаление элемента из конца очереди O(1)
peek_first() Доступ к элементу в начале очереди O(1)
peek_last() Доступ к элементу в конце очереди O(1)

Аналогично, можно использовать готовый класс двусторонней очереди в языке программирования:

=== "Python"

```python title="deque.py"
from collections import deque

# Инициализация двусторонней очереди
deq: deque[int] = deque()

# Добавление элемента в очередь
deq.append(2)      # Добавление в конец
deq.append(5)
deq.append(4)
deq.appendleft(3)  # Добавление в начало
deq.appendleft(1)

# Доступ к элементам
front: int = deq[0]  # Элемент в начале очереди
rear: int = deq[-1]  # Элемент в конце очереди

# Удаление элементов из очереди
pop_front: int = deq.popleft()  # Удаление из начала очереди
pop_rear: int = deq.pop()       # Удаление из конца очереди

# Получение длины двусторонней очереди
size: int = len(deq)

# Проверка двусторонней очереди на пустоту
is_empty: bool = len(deq) == 0
```

=== "C++"

```cpp title="deque.cpp"
/* Инициализация двусторонней очереди */
deque<int> deque;

/* Добавление элемента в очередь */
deque.push_back(2);   // Добавление в конец
deque.push_back(5);
deque.push_back(4);
deque.push_front(3);  // Добавление в начало
deque.push_front(1);

/* Доступ к элементам */
int front = deque.front(); // Элемент в начале очереди
int back = deque.back();   // Элемент в конце очереди

/* Удаление элементов из очереди */
deque.pop_front();  // Удаление из начала очереди
deque.pop_back();   // Удаление из конца очереди

/* Получение длины двусторонней очереди */
int size = deque.size();

/* Проверка двусторонней очереди на пустоту */
bool empty = deque.empty();
```

=== "Java"

```java title="deque.java"
/* Инициализация двусторонней очереди */
Deque<Integer> deque = new LinkedList<>();

/* Добавление элемента в очередь */
deque.offerLast(2);   // Добавление в конец
deque.offerLast(5);
deque.offerLast(4);
deque.offerFirst(3);  // Добавление в начало
deque.offerFirst(1);

/* Доступ к элементам */
int peekFirst = deque.peekFirst();  // Элемент в начале очереди
int peekLast = deque.peekLast();    // Элемент в конце очереди

/* Удаление элементов из очереди */
int popFirst = deque.pollFirst();  // Удаление из начала очереди
int popLast = deque.pollLast();    // Удаление из конца очереди

/* Получение длины двусторонней очереди */
int size = deque.size();

/* Проверка двусторонней очереди на пустоту */
boolean isEmpty = deque.isEmpty();
```

=== "C#"

```csharp title="deque.cs"
/* Инициализация двусторонней очереди */
// В C# связный список LinkedList используется как двусторонняя очередь
LinkedList<int> deque = new();

/* Добавление элемента в очередь */
deque.AddLast(2);   // Добавление в конец
deque.AddLast(5);
deque.AddLast(4);
deque.AddFirst(3);  // Добавление в начало
deque.AddFirst(1);

/* Доступ к элементам */
int peekFirst = deque.First.Value;  // Элемент в начале очереди
int peekLast = deque.Last.Value;    // Элемент в конце очереди

/* Удаление элементов из очереди */
deque.RemoveFirst();  // Удаление из начала очереди
deque.RemoveLast();   // Удаление из конца очереди

/* Получение длины двусторонней очереди */
int size = deque.Count;

/* Проверка двусторонней очереди на пустоту */
bool isEmpty = deque.Count == 0;
```

=== "Go"

```go title="deque_test.go"
/* Инициализация двусторонней очереди */
// В Go list используется как двусторонняя очередь
deque := list.New()

/* Добавление элемента в очередь */
deque.PushBack(2)      // Добавление в конец
deque.PushBack(5)
deque.PushBack(4)
deque.PushFront(3)     // Добавление в начало
deque.PushFront(1)

/* Доступ к элементам */
front := deque.Front() // Элемент в начале очереди
rear := deque.Back()   // Элемент в конце очереди

/* Удаление элементов из очереди */
deque.Remove(front)    // Удаление элемента из начала
deque.Remove(rear)     // Удаление элемента из конца

/* Получение длины двусторонней очереди */
size := deque.Len()

/* Проверка двусторонней очереди на пустоту */
isEmpty := deque.Len() == 0
```

=== "Swift"

```swift title="deque.swift"
/* Инициализация двусторонней очереди */
// В Swift нет встроенного класса двусторонней очереди, можно использовать Array
var deque: [Int] = []

/* Добавление элемента в очередь */
deque.append(2) // Добавление в конец
deque.append(5)
deque.append(4)
deque.insert(3, at: 0) // Добавление в начало
deque.insert(1, at: 0)

/* Доступ к элементам */
let peekFirst = deque.first! // Элемент в начале очереди
let peekLast = deque.last! // Элемент в конце очереди

/* Удаление элементов из очереди */
// При использовании Array сложность popFirst составляет O(n)
let popFirst = deque.removeFirst() // Удаление из начала очереди
let popLast = deque.removeLast() // Удаление из конца очереди

/* Получение длины двусторонней очереди */
let size = deque.count

/* Проверка двусторонней очереди на пустоту */
let isEmpty = deque.isEmpty
```

=== "JS"

```javascript title="deque.js"
/* Инициализация двусторонней очереди */
// В JavaScript нет встроенной двусторонней очереди, используется Array
const deque = [];

/* Добавление элемента в очередь */
deque.push(2);
deque.push(5);
deque.push(4);
// Обратите внимание: при использовании массива временная сложность unshift() составляет O(n)
deque.unshift(3);
deque.unshift(1);

/* Доступ к элементам */
const peekFirst = deque[0];
const peekLast = deque[deque.length - 1];

/* Удаление элементов из очереди */
// Обратите внимание: при использовании массива временная сложность shift() составляет O(n)
const popFront = deque.shift();
const popBack = deque.pop();

/* Получение длины двусторонней очереди */
const size = deque.length;

/* Проверка двусторонней очереди на пустоту */
const isEmpty = size === 0;
```

=== "TS"

```typescript title="deque.ts"
/* Инициализация двусторонней очереди */
// В TypeScript нет встроенной двусторонней очереди, используется Array
const deque: number[] = [];

/* Добавление элемента в очередь */
deque.push(2);
deque.push(5);
deque.push(4);
// Обратите внимание: при использовании массива временная сложность unshift() составляет O(n)
deque.unshift(3);
deque.unshift(1);

/* Доступ к элементам */
const peekFirst: number = deque[0];
const peekLast: number = deque[deque.length - 1];

/* Удаление элементов из очереди */
// Обратите внимание: при использовании массива временная сложность shift() составляет O(n)
const popFront: number = deque.shift() as number;
const popBack: number = deque.pop() as number;

/* Получение длины двусторонней очереди */
const size: number = deque.length;

/* Проверка двусторонней очереди на пустоту */
const isEmpty: boolean = size === 0;
```

=== "Dart"

```dart title="deque.dart"
/* Инициализация двусторонней очереди */
// В Dart Queue определяется как двусторонняя очередь
Queue<int> deque = Queue<int>();

/* Добавление элемента в очередь */
deque.addLast(2);  // Добавление в конец
deque.addLast(5);
deque.addLast(4);
deque.addFirst(3); // Добавление в начало
deque.addFirst(1);

/* Доступ к элементам */
int peekFirst = deque.first; // Элемент в начале очереди
int peekLast = deque.last;   // Элемент в конце очереди

/* Удаление элементов из очереди */
int popFirst = deque.removeFirst(); // Удаление из начала очереди
int popLast = deque.removeLast();   // Удаление из конца очереди

/* Получение длины двусторонней очереди */
int size = deque.length;

/* Проверка двусторонней очереди на пустоту */
bool isEmpty = deque.isEmpty;
```

=== "Rust"

```rust title="deque.rs"
/* Инициализация двусторонней очереди */
let mut deque: VecDeque<u32> = VecDeque::new();

/* Добавление элемента в очередь */
deque.push_back(2);  // Добавление в конец
deque.push_back(5);
deque.push_back(4);
deque.push_front(3); // Добавление в начало
deque.push_front(1);

/* Доступ к элементам */
if let Some(front) = deque.front() { // Элемент в начале очереди
}
if let Some(rear) = deque.back() {   // Элемент в конце очереди
}

/* Удаление элементов из очереди */
if let Some(pop_front) = deque.pop_front() { // Удаление из начала очереди
}
if let Some(pop_rear) = deque.pop_back() {   // Удаление из конца очереди
}

/* Получение длины двусторонней очереди */
let size = deque.len();

/* Проверка двусторонней очереди на пустоту */
let is_empty = deque.is_empty();
```

=== "C"

```c title="deque.c"
// В C нет встроенной двусторонней очереди
```

=== "Kotlin"

```kotlin title="deque.kt"
/* Инициализация двусторонней очереди */
val deque = LinkedList<Int>()

/* Добавление элемента в очередь */
deque.offerLast(2)  // Добавление в конец
deque.offerLast(5)
deque.offerLast(4)
deque.offerFirst(3) // Добавление в начало
deque.offerFirst(1)

/* Доступ к элементам */
val peekFirst = deque.peekFirst() // Элемент в начале очереди
val peekLast = deque.peekLast()   // Элемент в конце очереди

/* Удаление элементов из очереди */
val popFirst = deque.pollFirst() // Удаление из начала очереди
val popLast = deque.pollLast()   // Удаление из конца очереди

/* Получение длины двусторонней очереди */
val size = deque.size

/* Проверка двусторонней очереди на пустоту */
val isEmpty = deque.isEmpty()
```

=== "Ruby"

```ruby title="deque.rb"
# Инициализация двусторонней очереди
# В Ruby нет встроенной двусторонней очереди, используется Array
deque = []

# Добавление элемента в очередь
deque << 2
deque << 5
deque << 4
# Обратите внимание: при использовании массива временная сложность Array#unshift составляет O(n)
deque.unshift(3)
deque.unshift(1)

# Доступ к элементам
peek_first = deque.first
peek_last = deque.last

# Удаление элементов из очереди
# Обратите внимание: при использовании массива временная сложность Array#shift составляет O(n)
pop_front = deque.shift
pop_back = deque.pop

# Получение длины двусторонней очереди
size = deque.length

# Проверка двусторонней очереди на пустоту
is_empty = size.zero?
```

??? pythontutor "Визуализация выполнения"

https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20deq.append%282%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E5%B0%BE%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E9%A6%96%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%20rear%20%3D%22,%20rear%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_front%20%3D%22,%20pop_front%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_rear%20%3D%22,%20pop_rear%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false

Реализация двусторонней очереди *

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

Реализация на основе двусвязного списка

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

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

Головной и хвостовой узлы двусвязного списка рассматриваются как начало и конец двусторонней очереди. При этом реализуется возможность добавления и удаления узлов с обеих сторон, см. рис. 5.8.

=== "LinkedListDeque" Реализация двусторонней очереди на основе двусвязного списка

=== "push_last()" Добавление в конец очереди

=== "push_first()" Добавление в начало очереди

=== "pop_last()" Удаление из конца очереди

=== "pop_first()" Удаление из начала очереди

Ниже представлен код реализации:

[file]{linkedlist_deque}-[class]{linked_list_deque}-[func]{}

Реализация на основе массива

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

=== "ArrayDeque" Реализация двусторонней очереди на основе массива

=== "push_last()" Добавление в конец очереди

=== "push_first()" Добавление в начало очереди

=== "pop_last()" Удаление из конца очереди

=== "pop_first()" Удаление из начала очереди

По сравнению с реализацией обычной очереди необходимо лишь добавить методы для добавления в начало очереди и для удаления из конца очереди:

[file]{array_deque}-[class]{array_deque}-[func]{}

Применение двусторонней очереди

Двусторонняя очередь сочетает в себе логику стека и очереди. Поэтому она применима для всех сценариев этих двух структур, одновременно предоставляя большую степень свободы.

Известно, что функция отмены в программном обеспечении обычно реализуется с помощью стека: система помещает каждое изменение в стек с помощью операции push, а затем выполняет отмену с помощью операции pop. Однако, учитывая ограничения системных ресурсов, программное обеспечение обычно ограничивает количество шагов отмены (например, позволяет сохранить только 50 шагов). Когда длина стека превышает 50, программе нужно выполнить удаление внизу стека (в начале очереди). Но стек не может реализовать эту функцию, и в этом случае необходимо использовать двусторонюю очередь вместо стека. Следует отметить, что основная логика отмены по-прежнему следует принципу стека «первым пришел -- последним вышел», просто двусторонняя очередь позволяет более гибко реализовать некоторые дополнительные логические операции.