Очередь — структура данных, работающая по принципу FIFO (First In, First Out — первый вошёл, первый вышел). Как очередь в магазине: кто первый встал, тот первый обслужен.
Enqueue (добавление): Dequeue (извлечение):
-> | 4 | 3 | 2 | 1 | -> | 4 | 3 | 2 | -> 1 (удалён)
back front back front
Операции
Операция
Описание
Сложность
enqueue(x)
Добавить в конец
O(1)
dequeue()
Удалить из начала
O(1)*
peek/front()
Посмотреть первый элемент
O(1)
isEmpty()
Проверка на пустоту
O(1)
* O(1) только при подходящей структуре: SplQueue в PHP, collections.deque в Python, Queue<T> в C#. Удаление из начала обычного массива (array_shift, list.pop(0)) — O(n).
Реализация
// PHP: SplQueue — optimal queue implementation
$queue = new SplQueue();
$queue->enqueue(1); // [1]
$queue->enqueue(2); // [1, 2]
$queue->enqueue(3); // [1, 2, 3]
$queue->dequeue(); // 1, queue = [2, 3]
$queue->bottom(); // peek: 2
// WARNING: Do NOT use array_shift as dequeue!
// array_shift() = O(n), because it re-indexes all elements
package main
import "fmt"
func main() {
// Go: use a slice as a simple queue
queue := []int{}
queue = append(queue, 1) // [1]
queue = append(queue, 2) // [1, 2]
queue = append(queue, 3) // [1, 2, 3]
front := queue[0] // dequeue: 1
queue = queue[1:] // queue = [2, 3]
fmt.Println(front)
peek := queue[0] // peek: 2
fmt.Println(peek)
// For production use, consider container/list or
// a ring buffer to avoid O(n) slice re-allocation.
}
// C#: Queue<T> — built-in FIFO queue, O(1) enqueue/dequeue
var queue = new Queue<int>();
queue.Enqueue(1); // [1]
queue.Enqueue(2); // [1, 2]
queue.Enqueue(3); // [1, 2, 3]
queue.Dequeue(); // 1, queue = [2, 3]
queue.Peek(); // peek: 2
// WARNING: Do NOT use List<T>.RemoveAt(0) as dequeue!
// RemoveAt(0) = O(n), because it shifts all remaining elements
from collections import deque
# Python: collections.deque — optimal queue implementation
queue: deque[int] = deque()
queue.append(1) # [1]
queue.append(2) # [1, 2]
queue.append(3) # [1, 2, 3]
queue.popleft() # 1, queue = [2, 3]
front = queue[0] # peek: 2
# WARNING: Do NOT use list.pop(0) as dequeue!
# list.pop(0) = O(n), because it shifts all remaining elements
## Deque (Double-Ended Queue)
Двусторонняя очередь — можно добавлять и удалять с обоих концов.
class QueueViaStacks
{
private array $stackIn = [];
private array $stackOut = [];
public function enqueue(int $x): void
{
$this->stackIn[] = $x;
}
public function dequeue(): int
{
if (empty($this->stackOut)) {
while (!empty($this->stackIn)) {
$this->stackOut[] = array_pop($this->stackIn);
}
}
return array_pop($this->stackOut);
}
public function peek(): int
{
if (empty($this->stackOut)) {
while (!empty($this->stackIn)) {
$this->stackOut[] = array_pop($this->stackIn);
}
}
return end($this->stackOut);
}
}
// QueueViaStacks implements a FIFO queue using two stacks.
type QueueViaStacks struct {
stackIn []int
stackOut []int
}
func (q *QueueViaStacks) Enqueue(x int) {
q.stackIn = append(q.stackIn, x)
}
func (q *QueueViaStacks) transfer() {
if len(q.stackOut) == 0 {
for len(q.stackIn) > 0 {
top := q.stackIn[len(q.stackIn)-1]
q.stackIn = q.stackIn[:len(q.stackIn)-1]
q.stackOut = append(q.stackOut, top)
}
}
}
func (q *QueueViaStacks) Dequeue() int {
q.transfer()
val := q.stackOut[len(q.stackOut)-1]
q.stackOut = q.stackOut[:len(q.stackOut)-1]
return val
}
func (q *QueueViaStacks) Peek() int {
q.transfer()
return q.stackOut[len(q.stackOut)-1]
}
// QueueViaStacks implements a FIFO queue using two stacks.
public class QueueViaStacks
{
private readonly Stack<int> _stackIn = new();
private readonly Stack<int> _stackOut = new();
public void Enqueue(int x) => _stackIn.Push(x);
private void Transfer()
{
if (_stackOut.Count > 0)
{
return;
}
while (_stackIn.Count > 0)
{
_stackOut.Push(_stackIn.Pop());
}
}
public int Dequeue()
{
Transfer();
return _stackOut.Pop();
}
public int Peek()
{
Transfer();
return _stackOut.Peek();
}
}
# QueueViaStacks implements a FIFO queue using two stacks (lists).
class QueueViaStacks:
def __init__(self) -> None:
self._stack_in: list[int] = []
self._stack_out: list[int] = []
def enqueue(self, x: int) -> None:
self._stack_in.append(x)
def _transfer(self) -> None:
if self._stack_out:
return
while self._stack_in:
self._stack_out.append(self._stack_in.pop())
def dequeue(self) -> int:
self._transfer()
return self._stack_out.pop()
def peek(self) -> int:
self._transfer()
return self._stack_out[-1]
Амортизированно каждый элемент перекладывается **ровно один раз**, поэтому O(1) на операцию.
Задача 2: K-й наибольший элемент (с Priority Queue)
function kthLargest(array $nums, int $k): int
{
$heap = new SplMinHeap();
foreach (array_slice($nums, 0, $k) as $num) {
$heap->insert($num);
}
for ($i = $k; $i < count($nums); $i++) {
if ($nums[$i] > $heap->top()) {
$heap->extract();
$heap->insert($nums[$i]);
}
}
return $heap->top();
}
import "container/heap"
func kthLargest(nums []int, k int) int {
h := &IntMinHeap{}
heap.Init(h)
for i := 0; i < k; i++ {
heap.Push(h, nums[i])
}
for i := k; i < len(nums); i++ {
if nums[i] > (*h)[0] {
heap.Pop(h)
heap.Push(h, nums[i])
}
}
return (*h)[0]
}
static int KthLargest(int[] nums, int k)
{
// Min-heap of size k keeps the k largest elements seen so far
var heap = new PriorityQueue<int, int>();
for (var i = 0; i < k; i++)
{
heap.Enqueue(nums[i], nums[i]);
}
for (var i = k; i < nums.Length; i++)
{
if (nums[i] > heap.Peek())
{
heap.Dequeue();
heap.Enqueue(nums[i], nums[i]);
}
}
return heap.Peek();
}
import heapq
def kth_largest(nums: list[int], k: int) -> int:
# Min-heap of size k keeps the k largest elements seen so far
heap = nums[:k]
heapq.heapify(heap)
for num in nums[k:]:
if num > heap[0]:
heapq.heapreplace(heap, num) # pop + push in one sift
return heap[0]
# Standard library shortcut with the same O(n log k): heapq.nlargest(k, nums)[-1]
**Сложность:** O(n log k) — лучше чем сортировка O(n log n) при k << n.
Задача 3: BFS с очередью
function bfs(array $graph, int $start): array
{
$visited = [$start => true];
$queue = new SplQueue();
$queue->enqueue($start);
$order = [];
while (!$queue->isEmpty()) {
$node = $queue->dequeue();
$order[] = $node;
foreach ($graph[$node] as $neighbor) {
if (!isset($visited[$neighbor])) {
$visited[$neighbor] = true;
$queue->enqueue($neighbor);
}
}
}
return $order;
}
func bfs(graph map[int][]int, start int) []int {
visited := map[int]bool{start: true}
queue := []int{start}
order := []int{}
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
order = append(order, node)
for _, neighbor := range graph[node] {
if !visited[neighbor] {
visited[neighbor] = true
queue = append(queue, neighbor)
}
}
}
return order
}
static List<int> Bfs(Dictionary<int, List<int>> graph, int start)
{
var visited = new HashSet<int> { start };
var queue = new Queue<int>();
queue.Enqueue(start);
var order = new List<int>();
while (queue.Count > 0)
{
var node = queue.Dequeue();
order.Add(node);
foreach (var neighbor in graph[node])
{
if (visited.Add(neighbor)) // Add returns false if already present
{
queue.Enqueue(neighbor);
}
}
}
return order;
}
from collections import deque
def bfs(graph: dict[int, list[int]], start: int) -> list[int]:
visited = {start}
queue: deque[int] = deque([start])
order: list[int] = []
while queue:
node = queue.popleft()
order.append(node)
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return order
## Где используется
Структура
Применение
Queue
BFS, планировщики задач, буферы
Deque
Sliding window max/min, паттерны с двух сторон
Priority Queue (Heap)
Dijkstra, k-й элемент, merge k sorted lists
Запомни: Очередь = FIFO. В PHP используй SplQueue, никогда array_shift(). Priority Queue (heap) — когда нужно быстро получать min/max. Очередь из двух стеков — классика интервью.
Итоги
Queue = FIFO, все операции O(1)
Deque = операции с обоих концов за O(1)
Priority Queue = извлечение min/max за O(log n)
В PHP: SplQueue для очереди, SplPriorityQueue/SplMinHeap для приоритетной
BFS всегда использует очередь
Очередь из двух стеков — амортизированно O(1)
Проверь себя
5 из 8
Какая сложность операции insert в SplPriorityQueue (приоритетная очередь на куче)?
Почему в Python не стоит реализовывать dequeue через `arr.pop(0)` и что использовать вместо этого?
Для поиска K-го наибольшего элемента в массиве из n чисел используется SplMinHeap размера k. Какова итоговая сложность?
В Go dequeue часто пишут как `s = s[1:]`. Формально это O(1), но в чём подвох?
Почему в PHP нельзя использовать `array_shift()` для реализации dequeue?