Алгоритмы и структуры данных на Kotlin: практическое руководство

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

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

Начать курс

Алгоритмы и структуры данных на Kotlin: на практике

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

1. Базовые структуры данных: массивы и списки

В Kotlin массивы представлены типом Array<T>, а динамические списки — List<T> (неизменяемый) и MutableList<T> (изменяемый). Однако в реальных задачах часто требуется реализовать собственный связный список для понимания принципов работы.

Односвязный список на Kotlin

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

class Node<T>(var value: T, var next: Node<T>? = null)

class LinkedList<T> {
    private var head: Node<T>? = null

    fun add(value: T) {
        val newNode = Node(value)
        if (head == null) {
            head = newNode
        } else {
            var current = head
            while (current?.next != null) {
                current = current.next
            }
            current?.next = newNode
        }
    }

    fun printAll() {
        var current = head
        while (current != null) {
            print("${current.value} ")
            current = current.next
        }
        println()
    }
}

fun main() {
    val list = LinkedList<Int>()
    list.add(1)
    list.add(2)
    list.add(3)
    list.printAll() // Вывод: 1 2 3
}

Эта реализация демонстрирует базовые операции: добавление в конец и обход. В реальных проектах используйте встроенные коллекции Kotlin, но понимание внутреннего устройства помогает избегать ошибок при работе с памятью и производительностью.

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

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

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

QuickSort — один из самых эффективных алгоритмов со средней сложностью O(n log n). Реализация на Kotlin с использованием рекурсии:

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

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

fun main() {
    val array = intArrayOf(10, 7, 8, 9, 1, 5)
    quickSort(array, 0, array.size - 1)
    println(array.joinToString()) // Вывод: 1, 5, 7, 8, 9, 10
}

Обратите внимание на использование also для обмена значений — это идиоматичный способ в Kotlin. Алгоритм стабилен и работает in-place, что экономит память.

3. Поиск: бинарный и линейный

Поиск элементов в структурах данных — вторая по важности задача. Рассмотрим два подхода.

Линейный поиск

Простой, но медленный алгоритм (O(n)). Подходит для несортированных данных:

fun linearSearch(arr: IntArray, target: Int): Int {
    for (i in arr.indices) {
        if (arr[i] == target) return i
    }
    return -1
}

Бинарный поиск

Работает только на отсортированных массивах, но за O(log n). Реализация на Kotlin с использованием рекурсии:

fun binarySearch(arr: IntArray, target: Int, left: Int, right: Int): Int {
    if (left > right) return -1
    val mid = (left + right) / 2
    return when {
        arr[mid] == target -> mid
        arr[mid] < target -> binarySearch(arr, target, mid + 1, right)
        else -> binarySearch(arr, target, left, mid - 1)
    }
}

fun main() {
    val sortedArray = intArrayOf(1, 3, 5, 7, 9, 11)
    val index = binarySearch(sortedArray, 7, 0, sortedArray.size - 1)
    println("Индекс элемента: $index") // Вывод: 3
}

В Kotlin также есть встроенная функция binarySearch() для списков, но ручная реализация помогает понять механизм деления пополам.

4. Деревья и хеш-таблицы

Двоичные деревья поиска (BST) и хеш-таблицы — основа многих сложных структур данных, таких как HashMap в стандартной библиотеке Kotlin.

Двоичное дерево поиска (BST)

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

class TreeNode<T : Comparable<T>>(var value: T) {
    var left: TreeNode<T>? = null
    var right: TreeNode<T>? = null
}

class BinarySearchTree<T : Comparable<T>> {
    private var root: TreeNode<T>? = null

    fun insert(value: T) {
        root = insertRec(root, value)
    }

    private fun insertRec(node: TreeNode<T>?, value: T): TreeNode<T> {
        if (node == null) return TreeNode(value)
        when {
            value < node.value -> node.left = insertRec(node.left, value)
            value > node.value -> node.right = insertRec(node.right, value)
        }
        return node
    }

    fun contains(value: T): Boolean {
        return containsRec(root, value)
    }

    private fun containsRec(node: TreeNode<T>?, value: T): Boolean {
        if (node == null) return false
        return when {
            value == node.value -> true
            value < node.value                

Блоги

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