Временная сложность — это количество элементарных операций, которые выполняет алгоритм в зависимости от размера входных данных n. Мы не измеряем время в секундах, а считаем шаги.
Элементарные операции:
Сравнение двух чисел
Арифметическая операция (+, -, *, /)
Присваивание переменной
Доступ к элементу массива по индексу
Возврат значения из функции
Анализ простых циклов
Один цикл — O(n)
// Summing array elements
function sumArray(array $arr): int
{
$total = 0; // 1 operation
foreach ($arr as $x) { // n iterations
$total += $x; // 1 operation per iteration
}
return $total; // 1 operation
}
// Total: 1 + n*1 + 1 = n + 2 -> O(n)
// Summing slice elements
func sumArray(arr []int) int {
total := 0 // 1 operation
for _, x := range arr { // n iterations
total += x // 1 operation per iteration
}
return total // 1 operation
}
// Total: 1 + n*1 + 1 = n + 2 -> O(n)
// Summing array elements
static int SumArray(int[] arr)
{
int total = 0; // 1 operation
foreach (int x in arr) // n iterations
{
total += x; // 1 operation per iteration
}
return total; // 1 operation
}
// Total: 1 + n*1 + 1 = n + 2 -> O(n)
# Summing list elements
def sum_array(arr: list[int]) -> int:
total = 0 # 1 operation
for x in arr: # n iterations
total += x # 1 operation per iteration
return total # 1 operation
# Total: 1 + n*1 + 1 = n + 2 -> O(n)
func firstHalf(arr []int) {
half := len(arr) / 2
for i := 0; i < half; i++ { // n/2 iterations
fmt.Println(arr[i])
}
}
// n/2 -> O(n)
static void FirstHalf(int[] arr)
{
int half = arr.Length / 2;
for (int i = 0; i < half; i++) // n/2 iterations
{
Console.WriteLine(arr[i]);
}
}
// n/2 -> O(n)
def first_half(arr: list[int]) -> None:
half = len(arr) // 2
for i in range(half): # n/2 iterations
print(arr[i])
# n/2 -> O(n)
## Анализ вложенных циклов
Два вложенных цикла — O(n²)
// All pairs of elements
function allPairs(array $arr): void
{
$n = count($arr);
for ($i = 0; $i < $n; $i++) { // n iterations
for ($j = 0; $j < $n; $j++) { // n iterations inside
echo $arr[$i] . ' ' . $arr[$j] . "\n";
}
}
}
// n * n = n² -> O(n²)
// All pairs of elements
func allPairs(arr []int) {
n := len(arr)
for i := 0; i < n; i++ { // n iterations
for j := 0; j < n; j++ { // n iterations inside
fmt.Printf("%d %d\n", arr[i], arr[j])
}
}
}
// n * n = n² -> O(n²)
// All pairs of elements
static void AllPairs(int[] arr)
{
int n = arr.Length;
for (int i = 0; i < n; i++) // n iterations
{
for (int j = 0; j < n; j++) // n iterations inside
{
Console.WriteLine($"{arr[i]} {arr[j]}");
}
}
}
// n * n = n² -> O(n²)
# All pairs of elements
def all_pairs(arr: list[int]) -> None:
for a in arr: # n iterations
for b in arr: # n iterations inside
print(a, b)
# n * n = n² -> O(n²)
function triplets(array $arr): void
{
$n = count($arr);
for ($i = 0; $i < $n; $i++) {
for ($j = 0; $j < $n; $j++) {
for ($k = 0; $k < $n; $k++) {
echo $arr[$i] . ' ' . $arr[$j] . ' ' . $arr[$k] . "\n";
}
}
}
}
// n * n * n = n³ -> O(n³)
func triplets(arr []int) {
n := len(arr)
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
for k := 0; k < n; k++ {
fmt.Printf("%d %d %d\n", arr[i], arr[j], arr[k])
}
}
}
}
// n * n * n = n³ -> O(n³)
static void Triplets(int[] arr)
{
int n = arr.Length;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
for (int k = 0; k < n; k++)
{
Console.WriteLine($"{arr[i]} {arr[j]} {arr[k]}");
}
}
}
}
// n * n * n = n³ -> O(n³)
def triplets(arr: list[int]) -> None:
n = len(arr)
for i in range(n):
for j in range(n):
for k in range(n):
print(arr[i], arr[j], arr[k])
# n * n * n = n³ -> O(n³)
## Последовательные блоки
Когда блоки идут друг за другом, складываем сложности.
func mixedOperations(arr []int) {
n := len(arr)
// Block 1: O(n)
for _, x := range arr {
fmt.Println(x)
}
// Block 2: O(n²)
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
fmt.Printf("%d %d\n", i, j)
}
}
// Block 3: O(n)
for _, x := range arr {
fmt.Println(x * 2)
}
}
// O(n) + O(n²) + O(n) = O(n²)
// Take the highest degree
static void MixedOperations(int[] arr)
{
int n = arr.Length;
// Block 1: O(n)
foreach (int x in arr)
{
Console.WriteLine(x);
}
// Block 2: O(n²)
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
Console.WriteLine($"{i} {j}");
}
}
// Block 3: O(n)
foreach (int x in arr)
{
Console.WriteLine(x * 2);
}
}
// O(n) + O(n²) + O(n) = O(n²)
// Take the highest degree
def mixed_operations(arr: list[int]) -> None:
n = len(arr)
# Block 1: O(n)
for x in arr:
print(x)
# Block 2: O(n²)
for i in range(n):
for j in range(n):
print(i, j)
# Block 3: O(n)
for x in arr:
print(x * 2)
# O(n) + O(n²) + O(n) = O(n²)
# Take the highest degree
function doublingLoop(int $n): int
{
$i = 1;
$count = 0;
while ($i < $n) {
$i *= 2;
$count++;
}
return $count;
}
// n=16: i = 1,2,4,8,16 (4 steps)
// -> O(log n)
func doublingLoop(n int) int {
i := 1
count := 0
for i < n {
i *= 2
count++
}
return count
}
// n=16: i = 1,2,4,8,16 (4 steps)
// -> O(log n)
static int DoublingLoop(int n)
{
int i = 1;
int count = 0;
while (i < n)
{
i *= 2;
count++;
}
return count;
}
// n=16: i = 1,2,4,8,16 (4 steps)
// -> O(log n)
def doubling_loop(n: int) -> int:
i = 1
count = 0
while i < n:
i *= 2
count += 1
return count
# n=16: i = 1,2,4,8,16 (4 steps)
# -> O(log n)
### Цикл внутри логарифмического — O(n log n)
function nLogNExample(array $arr): void
{
$n = count($arr);
$step = 1;
while ($step < $n) { // log n iterations
for ($i = 0; $i < $n; $i++) { // n iterations inside
echo "$step $i\n";
}
$step *= 2;
}
}
// log(n) * n = O(n log n)
func nLogNExample(arr []int) {
n := len(arr)
step := 1
for step < n { // log n iterations
for i := 0; i < n; i++ { // n iterations inside
fmt.Printf("%d %d\n", step, i)
}
step *= 2
}
}
// log(n) * n = O(n log n)
static void NLogNExample(int[] arr)
{
int n = arr.Length;
int step = 1;
while (step < n) // log n iterations
{
for (int i = 0; i < n; i++) // n iterations inside
{
Console.WriteLine($"{step} {i}");
}
step *= 2;
}
}
// log(n) * n = O(n log n)
def n_log_n_example(arr: list[int]) -> None:
n = len(arr)
step = 1
while step < n: # log n iterations
for i in range(n): # n iterations inside
print(step, i)
step *= 2
# log(n) * n = O(n log n)
## Анализ рекурсии
Линейная рекурсия — O(n)
// Factorial: n -> (n-1) -> (n-2) -> ... -> 1
function factorial(int $n): int
{
if ($n <= 1) {
return 1;
}
return $n * factorial($n - 1);
}
// Recursion depth: n
// Work at each level: O(1)
// Total: O(n)
// Factorial: n -> (n-1) -> (n-2) -> ... -> 1
func factorial(n int) int {
if n <= 1 {
return 1
}
return n * factorial(n-1)
}
// Recursion depth: n
// Work at each level: O(1)
// Total: O(n)
// Factorial: n -> (n-1) -> (n-2) -> ... -> 1
static int Factorial(int n)
{
if (n <= 1)
{
return 1;
}
return n * Factorial(n - 1);
}
// Recursion depth: n
// Work at each level: O(1)
// Total: O(n)
# Factorial: n -> (n-1) -> (n-2) -> ... -> 1
def factorial(n: int) -> int:
if n <= 1:
return 1
return n * factorial(n - 1)
# Recursion depth: n
# Work at each level: O(1)
# Total: O(n)
# Note: CPython's default recursion limit is 1000 frames
### Двоичная рекурсия — O(2^n)
// Naive Fibonacci
function fib(int $n): int
{
if ($n <= 1) {
return $n;
}
return fib($n - 1) + fib($n - 2);
}
// Naive Fibonacci
func fib(n int) int {
if n <= 1 {
return n
}
return fib(n-1) + fib(n-2)
}
Один цикл по n элементов -> O(n)
Два вложенных цикла по n -> O(n²)
Цикл с делением пополам -> O(log n)
Цикл внутри деления пополам -> O(n log n)
Рекурсия с двумя ветвями, глубина n -> O(2^n)
Рекурсия с двумя ветвями, деление пополам -> O(n log n)
Перебор всех подмножеств -> O(2^n)
Перебор всех перестановок -> O(n!)
Практика: определи сложность
Задача 1
function mystery1(int $n): int
{
$count = 0;
$i = $n;
while ($i > 0) {
for ($j = 0; $j < $n; $j++) {
$count++;
}
$i = intdiv($i, 2);
}
return $count;
}
func mystery1(n int) int {
count := 0
i := n
for i > 0 {
for j := 0; j < n; j++ {
count++
}
i = i / 2
}
return count
}
static int Mystery1(int n)
{
int count = 0;
int i = n;
while (i > 0)
{
for (int j = 0; j < n; j++)
{
count++;
}
i /= 2;
}
return count;
}
def mystery1(n: int) -> int:
count = 0
i = n
while i > 0:
for _ in range(n):
count += 1
i //= 2
return count
def mystery3(n: int) -> int:
if n <= 1:
return 1
return mystery3(n // 2) + mystery3(n // 2)
Ответ: Два рекурсивных вызова, каждый с n/2. T(n) = 2T(n/2) + O(1). По мастер-теореме: a=2, b=2, d=0 → log_2(2) = 1 > 0 → **O(n)**.
Запомни: Чтобы определить временную сложность: 1) Посчитай количество итераций каждого цикла, 2) Умножь вложенные, сложи последовательные, 3) Для рекурсии — нарисуй дерево вызовов или используй мастер-теорему. 4) Отбрось константы и младшие члены.
Итоги
Простой цикл по всем элементам = O(n)
Вложенные циклы = произведение итераций
Деление пополам в любой форме = log n
Рекурсия: считай количество вызовов и работу в каждом