Стек — LIFO, очередь — FIFO, двоичная куча отдаёт минимум или максимум за O(log n). В Go слайс закрывает стек и простую очередь, а container/heap — приоритет; куча процесса с GC к этой структуре не относится.
Алгоритмы и структуры данных cs собеседование
Эти слова описывают контракт, а не конкретный массив.
Стек: положили последним — сняли первым. Это LIFO. Отмена, скобки, стек вызовов.
Очередь: первым пришёл — первым ушёл. Это FIFO. Воркеры, список задач.
Deque умеет оба конца. Двоичная куча (binary heap) — не «очередь с приоритетом вообще», а конкретная реализация: дерево в массиве, где родитель не хуже детей.
Пример. Стек тарелок: сняли верхнюю. Очередь в кассу: кто пришёл первым, того первым обслужили. Куча как структура — приём в больнице: на вершине всегда самый срочный. Не путайте с «кучей процесса», откуда GC забирает объекты. Слово одно, вещи разные.
Стек вызовов функции и stack как структура данных — разная жизнь, одна идея LIFO. Горутина растёт свой стек сама. Это не []int, который вы append'ите.
Priority queue на куче нужна планировщику, таймерам («ближайший дедлайн»), top-K, слиянию отсортированных потоков. Извлечение минимума из бинарной кучи — O(log n): сняли корень, на его место последний элемент, просеяли вниз. Подсмотреть минимум — O(1). После извлечения кучу надо восстановить. Поэтому «всегда O(1) без восстановления» — неверно.
Куча как массив
Индекс i, дети 2i+1 и 2i+2. Вставка — в конец и просеивание вверх, O(log n). Это не отсортированный массив. Порядок внутри уровня не обещан. Обещан только heap property: родитель не хуже детей.
Чтобы реализовать интерфейс heap.Interface в Go, нужны Len, Less, Swap, Push, Pop.
type minHeap []int
func (h minHeap) Len() int { return len(h) }
func (h minHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h minHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *minHeap) Push(x any) { *h = append(*h, x.(int)) }
func (h *minHeap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
// heap.Init(&h); heap.Push(&h, 3); min := heap.Pop(&h).(int)
Минимальная куча через container/heap. Extract-min — Pop, сложность O(log n).
Очередь на слайсе
Стек на слайсе естественен: append и s[:len-1]. Очередь «головой в начале» через s = s[1:] выглядит O(1) по времени. Но оставляет неиспользуемый префикс backing array: len падает, cap спереди мёртвый. Долгая очередь так раздует память.
Пример. Слайс из 1000 задач. Сняли 900 с головы через s = s[1:]. Живых элементов 100, а массив на 1000 всё ещё держится: cap смотрит на старый префикс. Нужен ring buffer, смещение головы с периодическим compact, или контейнер из двух стеков.
Что выбрать
- Воркеры задач — FIFO, не стек: иначе последний запрос голодает первых.
- Таймауты и «кто раньше истёк» — куча по дедлайну.
- Отмена вложенных операций, парсер, undo — стек.
- Deque — скользящее окно, обе стороны очереди.
Типичная ошибка
Путать binary heap со сборщиком мусора и «кучей процесса». И вторая: считать dequeue через сдвиг головы на одном слайсе бесплатным по памяти — префикс массива остаётся живым, пока на него есть ссылка через cap.
На собеседовании
ADT и сложность: extract-min из кучи O(log n), очередь воркеров — FIFO. Почему s = s[1:] на длинной жизни — ловушка памяти. Чем heap в container/heap отличается от heap в pprof.