← Назад к вопросам

Реализовать Queue на слайсе

2.0 Middle🔥 141 комментариев
#Основы Go

Условие

Реализуйте структуру данных Queue (FIFO) на основе слайса.

Интерфейс

type Queue struct {
    // ваши поля
}

func NewQueue() *Queue
func (q *Queue) Enqueue(val int)
func (q *Queue) Dequeue() (int, bool)
func (q *Queue) Front() (int, bool)
func (q *Queue) IsEmpty() bool
func (q *Queue) Size() int

Требования

  • Enqueue добавляет элемент в конец очереди
  • Dequeue удаляет и возвращает элемент из начала (false если очередь пуста)
  • Front возвращает первый элемент без удаления
  • IsEmpty проверяет, пуста ли очередь

Пример

q := NewQueue()
q.Enqueue(1)
q.Enqueue(2)
q.Enqueue(3)
val, _ := q.Dequeue() // 1
val, _ = q.Front() // 2

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение: Реализовать Queue на слайсе

Основная идея

Queue (очередь) — структура данных FIFO (First In, First Out). Элементы добавляются с конца и удаляются с начала. На слайсе это неэффективно для каждого `Dequeue()`, так как нужно смещать все элементы. Однако для учебных целей можно использовать простой подход или оптимизированный с индексами.

Наивный подход: с копированием

type Queue struct {
    items []int
}

func NewQueue() *Queue {
    return &Queue{
        items: make([]int, 0),
    }
}

func (q *Queue) Enqueue(val int) {
    q.items = append(q.items, val)
}

func (q *Queue) Dequeue() (int, bool) {
    if len(q.items) == 0 {
        return 0, false
    }
    val := q.items[0]
    q.items = q.items[1:]
    return val, true
}

func (q *Queue) Front() (int, bool) {
    if len(q.items) == 0 {
        return 0, false
    }
    return q.items[0], true
}

func (q *Queue) IsEmpty() bool {
    return len(q.items) == 0
}

func (q *Queue) Size() int {
    return len(q.items)
}

Проблемы: Dequeue() имеет O(n) сложность из-за смещения элементов. Для большой очереди это неэффективно.

Оптимизированный подход: с индексами (рекомендуется)

type Queue struct {
    items []int
    front int // индекс первого элемента
    rear  int // индекс для добавления нового элемента
    size  int // текущий размер
}

func NewQueue() *Queue {
    return &Queue{
        items: make([]int, 10), // начальная ёмкость
        front: 0,
        rear:  0,
        size:  0,
    }
}

func (q *Queue) Enqueue(val int) {
    // Если очередь заполнена, расширяем
    if q.size == len(q.items) {
        q.resize()
    }
    
    q.items[q.rear] = val
    q.rear = (q.rear + 1) % len(q.items) // циклический буфер
    q.size++
}

func (q *Queue) Dequeue() (int, bool) {
    if q.size == 0 {
        return 0, false
    }
    
    val := q.items[q.front]
    q.front = (q.front + 1) % len(q.items) // циклический буфер
    q.size--
    
    return val, true
}

func (q *Queue) Front() (int, bool) {
    if q.size == 0 {
        return 0, false
    }
    return q.items[q.front], true
}

func (q *Queue) IsEmpty() bool {
    return q.size == 0
}

func (q *Queue) Size() int {
    return q.size
}

func (q *Queue) resize() {
    // Новая ёмкость в 2 раза больше
    newCapacity := len(q.items) * 2
    newItems := make([]int, newCapacity)
    
    // Копируем элементы в новый слайс в правильном порядке
    for i := 0; i < q.size; i++ {
        newItems[i] = q.items[(q.front+i)%len(q.items)]
    }
    
    q.items = newItems
    q.front = 0
    q.rear = q.size
}

Преимущества:

  • Enqueue: O(1) в среднем (при амортизации)
  • Dequeue: O(1) всегда
  • Память эффективно использует циклический буфер

Пример использования

q := NewQueue()

// Добавляем элементы
q.Enqueue(1)
q.Enqueue(2)
q.Enqueue(3)
fmt.Println("Size:", q.Size()) // 3

// Читаем первый элемент
val, _ := q.Front()
fmt.Println("Front:", val) // 2

// Удаляем элемент
val, ok := q.Dequeue()
fmt.Println("Dequeue:", val, ok) // 1, true

fmt.Println("Size:", q.Size()) // 2
fmt.Println("IsEmpty:", q.IsEmpty()) // false

// Удаляем оставшиеся
q.Dequeue() // 2
q.Dequeue() // 3
fmt.Println("IsEmpty:", q.IsEmpty()) // true

// Пустая очередь
val, ok = q.Dequeue()
fmt.Println("Dequeue from empty:", val, ok) // 0, false

Версия с циклическим буфером (более компактно)

type Queue struct {
    data  []int
    front int
    size  int
}

func NewQueue() *Queue {
    return &Queue{
        data: make([]int, 10),
    }
}

func (q *Queue) Enqueue(val int) {
    if q.size == len(q.data) {
        q.resize()
    }
    index := (q.front + q.size) % len(q.data)
    q.data[index] = val
    q.size++
}

func (q *Queue) Dequeue() (int, bool) {
    if q.size == 0 {
        return 0, false
    }
    val := q.data[q.front]
    q.front = (q.front + 1) % len(q.data)
    q.size--
    return val, true
}

func (q *Queue) Front() (int, bool) {
    if q.size == 0 {
        return 0, false
    }
    return q.data[q.front], true
}

func (q *Queue) IsEmpty() bool {
    return q.size == 0
}

func (q *Queue) Size() int {
    return q.size
}

func (q *Queue) resize() {
    newData := make([]int, len(q.data)*2)
    for i := 0; i < q.size; i++ {
        newData[i] = q.data[(q.front+i)%len(q.data)]
    }
    q.data = newData
    q.front = 0
}

Сложность операций

ОперацияНаивныйС индексами
EnqueueO(1)O(1) amortized
DequeueO(n)O(1)
FrontO(1)O(1)
SizeO(1)O(1)

Ключевые моменты

  • Циклический буфер: (index + 1) % capacity позволяет переиспользовать ячейки
  • Resize при заполнении: амортизированная O(1) для Enqueue
  • Индексы вместо копирования: избегает O(n) операции для каждого Dequeue
  • Потокобезопасность: если нужна, используйте мьютекс или sync.Queue
  • Дженерики: в Go 1.18+ можно использовать type Queue[T any] для универсальности

Это стандартная реализация очереди, которая используется в production кодобазах.

Реализовать Queue на слайсе | PrepBro