1. Введение в алгоритмизацию: оценка сложности (Big O) и базовые конструкции Kotlin
Введение в алгоритмизацию: оценка сложности (Big O) и базовые конструкции Kotlin
Добро пожаловать на курс «Алгоритмы и структуры данных на языке Kotlin». Мы начинаем наше путешествие с фундаментальных понятий, без которых невозможно стать профессиональным разработчиком. Сегодня мы разберем, что такое алгоритм, почему важно уметь оценивать его эффективность и как язык Kotlin помогает писать чистый и безопасный код.
Что такое алгоритм?
В самом широком смысле алгоритм — это четкая последовательность действий, приводящая к решению поставленной задачи за конечное число шагов. Это не просто код, это рецепт.
Представьте, что вы готовите яичницу. Ваш алгоритм может выглядеть так:
В программировании алгоритмы работают с данными. Сортировка списка контактов в телефоне, построение маршрута в навигаторе или рекомендация видео на YouTube — все это результат работы алгоритмов.
Свойства хорошего алгоритма
Чтобы инструкция считалась алгоритмом, она должна обладать следующими свойствами:
* Дискретность: процесс разбит на отдельные простые шаги.
* Определенность (детерминированность): каждый шаг понятен однозначно, нет места для двоякого толкования. При одних и тех же входных данных результат всегда одинаков.
* Конечность: алгоритм должен завершаться за разумное время.
* Массовость: алгоритм должен работать для целого класса задач, а не только для одного конкретного случая (например, сортировать любой массив чисел, а не только массив [1, 5, 3]).
Оценка сложности алгоритмов: Big O Notation
Написать работающий код — это только полдела. Важно написать эффективный код. Но как измерить эффективность? Секундомером? Это плохой способ, так как время выполнения зависит от мощности компьютера, загруженности системы и даже температуры процессора.
Нам нужен универсальный способ оценки, который не зависит от «железа». Для этого используется нотация Big O («О» большое).
Big O описывает, как меняется время выполнения алгоритма (или потребляемая память) при увеличении объема входных данных.
!График сравнения временной сложности алгоритмов: от константной до экспоненциальной
Рассмотрим основные классы сложности, с которыми вы будете сталкиваться чаще всего.
— Константная сложность
Время выполнения не зависит от количества данных. Неважно, 10 элементов в массиве или 10 миллионов — операция займет одно и то же время.
Пример из жизни: вы берете книгу с полки, зная её точное местоположение.
— Линейная сложность
Время выполнения растет прямо пропорционально количеству данных. Если данных стало в 2 раза больше, алгоритм будет работать в 2 раза дольше.
Формально это можно записать так:
где — время выполнения, — некоторая константа (время на одну операцию), а — количество элементов.
Пример из жизни: чтение книги страница за страницей. Чем толще книга, тем дольше вы читаете.
— Квадратичная сложность
Время выполнения растет пропорционально квадрату количества элементов. Это часто встречается в алгоритмах с вложенными циклами. Если данных стало в 2 раза больше, время увеличится в 4 раза ().
Математически это выражается как:
где — время выполнения, — константа, — количество элементов, а показывает квадратичную зависимость.
— Логарифмическая сложность
Очень эффективная сложность. Время растет очень медленно при увеличении данных. Характерна для алгоритмов, которые на каждом шаге отсекают половину данных (например, бинарный поиск).
Формула:
где — время, — константа, — двоичный логарифм от количества элементов .
> «Преждевременная оптимизация — корень всех зол». > — Дональд Кнут [The Art of Computer Programming]
Это означает, что не стоит сразу гнаться за , если ваш код становится нечитаемым, а данных мало. Но понимать сложность необходимо, чтобы не написать алгоритм, который «повесит» сервер при нагрузке.
Базовые конструкции Kotlin для алгоритмов
Kotlin — современный, статически типизированный язык, который идеально подходит для реализации алгоритмов благодаря своей лаконичности и безопасности. Рассмотрим конструкции, которые мы будем использовать на протяжении всего курса.
Переменные: val и var
В алгоритмах важно понимать, меняется ли состояние переменной.
* val (value) — неизменяемая ссылка (аналог final в Java или const в C++). Используйте её по умолчанию.
* var (variable) — изменяемая переменная.
Циклы и диапазоны
Большинство алгоритмов требуют итерации (перебора). В Kotlin цикл for работает с диапазонами и коллекциями очень элегантно.
Функции
Алгоритмы оформляются в виде функций. В Kotlin функции объявляются ключевым словом fun.
Примеры алгоритмов на Kotlin с разбором сложности
Давайте реализуем несколько простых алгоритмов и оценим их сложность, используя полученные знания.
1. Линейный поиск (Linear Search)
Задача: Найти, есть ли определенное число в массиве.
Алгоритм: Идем по массиву слева направо и сравниваем каждый элемент с искомым.
Анализ сложности: В худшем случае (элемента нет или он в самом конце) нам придется проверить все элементов. Следовательно, сложность — .
2. Проверка на дубликаты (Naive approach)
Задача: Проверить, есть ли в массиве повторяющиеся числа.
Алгоритм: Берем первый элемент и сравниваем его со всеми остальными. Потом берем второй и сравниваем с оставшимися, и так далее.
Анализ сложности: Здесь мы видим вложенные циклы. Внешний цикл выполняется раз. Внутренний цикл выполняется примерно раз для каждой итерации внешнего.
Общее количество операций пропорционально:
где — количество операций, а — размер входных данных.
Сложность — . Это медленный алгоритм. Если , то количество операций будет около .
3. Доступ к элементу массива
Задача: Получить первый элемент списка.
Анализ сложности: Компьютер знает адрес начала массива в памяти и может мгновенно вычислить адрес любого элемента по индексу. Неважно, какой длины массив. Сложность — .
Итоги
Сегодня мы заложили фундамент для всего курса:
val, for, ranges) для реализации алгоритмов.В следующей статье мы подробно разберем массивы и списки, узнаем разницу между Array и ArrayList в Kotlin, и научимся писать эффективные алгоритмы для работы с ними.