Алгоритмы и структуры данных на Kotlin: полный гайд для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: зачем изучать алгоритмы и структуры данных на Kotlin?

Kotlin — современный, лаконичный и безопасный язык программирования, который активно используется для разработки под Android, бэкенд-сервисов и даже десктопных приложений. Но умение писать код на Kotlin — это только половина успеха. Чтобы стать действительно хорошим разработчиком, нужно понимать, как работают алгоритмы и структуры данных.

Зачем это нужно новичку? Во-первых, знание базовых структур (списки, стеки, очереди, хеш-таблицы) помогает писать более эффективный и быстрый код. Во-вторых, алгоритмы — это основа для решения технических задач на собеседованиях. В-третьих, это просто интересно: вы начинаете видеть за кодом логику и математику.

В этой статье мы разберём ключевые алгоритмы и структуры данных на Kotlin с нуля. Все примеры будут простыми, понятными и готовыми к запуску. Поехали!

1. Базовые структуры данных в Kotlin

Массивы и списки

В Kotlin есть два основных типа коллекций: Array (фиксированный размер) и List (динамический). Для большинства задач удобнее использовать List.

fun main() {
    // Массив фиксированного размера
    val numbersArray = arrayOf(1, 2, 3, 4, 5)
    println("Массив: ${numbersArray.joinToString()}")

    // Изменяемый список
    val mutableList = mutableListOf(10, 20, 30)
    mutableList.add(40)
    println("Список после добавления: $mutableList")

    // Неизменяемый список
    val immutableList = listOf(100, 200, 300)
    println("Неизменяемый список: $immutableList")
}

Стек (Stack)

Стек работает по принципу LIFO (Last In, First Out) — последний пришёл, первый ушёл. В Kotlin нет встроенного класса Stack, но его легко реализовать с помощью MutableList.

class Stack<T> {
    private val elements = mutableListOf<T>()

    fun push(item: T) = elements.add(item)
    fun pop(): T? = if (isEmpty()) null else elements.removeAt(elements.size - 1)
    fun peek(): T? = elements.lastOrNull()
    fun isEmpty() = elements.isEmpty()
    fun size() = elements.size
}

fun main() {
    val stack = Stack<Int>()
    stack.push(1)
    stack.push(2)
    stack.push(3)

    println("Верхний элемент: ${stack.peek()}") // 3
    println("Извлекаем: ${stack.pop()}")        // 3
    println("Теперь верхний: ${stack.peek()}")  // 2
}

Очередь (Queue)

Очередь работает по принципу FIFO (First In, First Out) — первый пришёл, первый ушёл. В Kotlin для этого удобно использовать LinkedList из Java.

import java.util.LinkedList

class Queue<T> {
    private val elements = LinkedList<T>()

    fun enqueue(item: T) = elements.add(item)
    fun dequeue(): T? = elements.poll()
    fun peek(): T? = elements.peek()
    fun isEmpty() = elements.isEmpty()
    fun size() = elements.size
}

fun main() {
    val queue = Queue<String>()
    queue.enqueue("Первый")
    queue.enqueue("Второй")
    queue.enqueue("Третий")

    println("Обслуживаем: ${queue.dequeue()}") // Первый
    println("Следующий: ${queue.peek()}")      // Второй
}

2. Алгоритмы сортировки на Kotlin

Пузырьковая сортировка (Bubble Sort)

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

fun bubbleSort(arr: MutableList<Int>) {
    val n = arr.size
    for (i in 0 until n - 1) {
        for (j in 0 until n - i - 1) {
            if (arr[j] > arr[j + 1]) {
                val temp = arr[j]
                arr[j] = arr[j + 1]
                arr[j + 1] = temp
            }
        }
    }
}

fun main() {
    val list = mutableListOf(64, 34, 25, 12, 22, 11, 90)
    bubbleSort(list)
    println("Отсортировано пузырьком: $list")
}

Быстрая сортировка (Quick Sort)

Один из самых эффективных алгоритмов. Работает по принципу "разделяй и властвуй".

fun quickSort(arr: MutableList<Int>, low: Int, high: Int) {
    if (low < high) {
        val pi = partition(arr, low, high)
        quickSort(arr, low, pi - 1)
        quickSort(arr, pi + 1, high)
    }
}

private fun partition(arr: MutableList<Int>, low: Int, high: Int): Int {
    val pivot = arr[high]
    var i = low - 1
    for (j in low until high) {
        if (arr[j] < pivot) {
            i++
            val temp = arr[i]
            arr[i] = arr[j]
            arr[j] = temp
        }
    }
    val temp = arr[i + 1]
    arr[i + 1] = arr[high]
    arr[high] = temp
    return i + 1
}

fun main() {
    val list = mutableListOf(10, 7, 8, 9, 1, 5)
    quickSort(list, 0, list.size - 1)
    println("Быстрая сортировка: $list")
}

3. Алгоритмы поиска

Линейный поиск (Linear Search)

Самый простой способ найти элемент — пройтись по всему списку.

fun linearSearch(arr: List<Int>, target: Int): Int? {
    for ((index, value) in arr.withIndex()) {
        if (value == target) return index
    }
    return null
}

fun main() {
    val numbers = listOf(5, 3, 8, 1, 9, 2)
    val target = 8
    val result = linearSearch(numbers, target)
    println("Индекс числа $target: ${result ?: "не найден"}")
}

Бинарный поиск (Binary Search)

Работает только на отсортированных данных. Делит массив пополам на каждом шаге, что даёт скорость O(log n).

fun binarySearch(arr: List<Int>, target: Int): Int? {
    var left = 0
    var right = arr.size - 1

    while (left <= right) {
        val mid = left + (right - left) / 2
        when {
            arr[mid] == target -> return mid
            arr[mid] < target -> left                

Блоги

Рекомендаций по книгам