В этом уроке ты
- Вспомнишь, что такое логарифм.
- Узнаешь, в каких случаях возникает сложность
.O(n*log(n)) - Научишься оценивать сортировки по времени и памяти.
Что такое log(n)
Логарифм log_b(N) отвечает на вопрос: «Сколько раз нужно разделить N на b, чтобы в результате получить 1?». b - это основание логарифма.
Для log_2(N): если N = 8 , то деления 8 → 4 → 2 → 1 занимают 3 шага (log_2(8)=3).
Обычно в записи O(log(n)) не указывают основание логарифма, потому что при асимптотической оценке важна только сама форма зависимости, а основание — константа.
Место n*log(n) в оценке сложности
n*log(n) называют линейно-логарифмической сложностью. По скорости работы она расположена между O(n) (линейной) и O(n*n) (квадратичной). Чтобы понять, как сильно n*log(n) отличается от n, взгляни на таблицу:
| n | Число операций в |
Число операций в |
|---|---|---|
| 16 = 2^4 | 16 * 4 = 64 | 16 |
| 1024 = 2^10 | 1024 * 10 = 10240 | 1024 |
| 65536 = 2^16 | 65536 * 16 = 1048576 | 65536 |
| 4294967296 = 2^32 | 4294967296 * 32 = 137438953472 | 4294967296 |
Хотя n*log(n) растёт быстрее, чем n, на практике такая сложность всё равно считается достаточно быстрой и ближе к O(n), чем к O(n*n).
Где встречается оценка O(n*log(n))
Чаще всего такая оценка возникает в алгоритмах сортировки:
using System;
using System.Collections.Generic;
public class Solution {
static Solution() {
List<int> nums = new List<int> {1, 5, 2, 3, 5, 1, 2, 3};
// Сортировка за O(n*log(n))
nums.Sort();
// [1, 1, 2, 2, 3, 3, 5, 5]
Console.WriteLine(string.Join(" ", nums));
}
}
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
// Сортировка за O(n*log(n))
__attribute__((constructor)) void run() {
vector<int> nums = {1, 5, 2, 3, 5, 1, 2, 3};
sort(nums.begin(), nums.end());
// [1, 1, 2, 2, 3, 3, 5, 5]
for (int num : nums) {
cout << num << " ";
}
cout << endl;
}
package main
import (
"fmt"
"sort"
)
func init() {
nums := []int{1, 5, 2, 3, 5, 1, 2, 3}
// Сортировка за O(n*log(n))
sort.Ints(nums)
// [1, 1, 2, 2, 3, 3, 5, 5]
fmt.Println(nums)
}
import java.util.*;
public class Solution {
static {
List<Integer> nums = new ArrayList<>(Arrays.asList(1, 5, 2, 3, 5, 1, 2, 3));
// Сортировка за O(n*log(n))
Collections.sort(nums);
// [1, 1, 2, 2, 3, 3, 5, 5]
System.out.println(nums);
}
}
nums = [1,5,2,3,5,1,2,3]
# Сортировка за O(n*log(n))
nums.sort()
# [1, 1, 2, 2, 3, 3, 5, 5]
print(nums)
// Сортировка за O(n*log(n))
export function run() {
const nums = [1, 5, 2, 3, 5, 1, 2, 3];
nums.sort((a, b) => a - b);
// [1, 1, 2, 2, 3, 3, 5, 5]
console.log(nums);
}
Также сложность встречается и в других алгоритмах, но в этом курсе мы их рассматривать не будем, так как они редко встречаются на практике.O(n*log(n))
Сортировки
Сортировка — это упорядочивание набора данных (чисел, слов или других объектов) по определённому правилу (например, по возрастанию или убыванию).
Пример: если мы отсортируем массив по возрастанию, получим [4,2,5,6,1].[1,2,4,5,6]
Время и память сортировок
Существует множество алгоритмов сортировки, и каждый из них лучше работает для тех или иных данных. Чаще всего сортировки имеют оценку по времени , а по памяти — O(n*log(n)), O(1) или O(log(n)).O(n)
Ниже примеры популярных сортировок, которые встроены в разные языки программирования:
| Алгоритм | Худший случай | Средний случай | Лучший случай | Память |
|---|---|---|---|---|
| Introsort | O(n*log(n)) | Θ(n*log(n)) | O(n*log(n)) | O(log(n)) |
| Timsort | O(n*log(n)) | Θ(n*log(n)) | O(n) | O(n) |
| Quick Sort | O(n*n) | Θ(n*log(n)) | O(n*log(n)) | O(log n) в среднем, O(n) в худшем |
| Heap Sort | O(n*log(n)) | Θ(n*log(n)) | O(n*log(n)) | O(1) |
Такие оценки по времени и памяти связаны с внутренним устройством каждого алгоритма. Мы не будем подробно разбирать их в этом курсе. Важно лишь помнить, что большинство эффективных сортировок имеют время работы порядка O(n*log(n)).
Дополнительное задание: узнай, какая сортировка используется в языке программирования, которым ты пользуешься, и выясни, какова её оценка по времени и памяти.
Главные выводы
- Чаще всего сортировка оценивается по времени как
, а по памяти —O(n*log(n)),O(n)илиO(log(n))в зависимости от конкретного алгоритма.O(1) - Оценка
чаще всего возникает при использовании сортировок.O(n*log(n)) - Логарифм
log_b(N)отвечает на вопрос: “Сколько раз нужно разделить N на b, чтобы получить 1?”