← Назад к вопросам
Приведи пример любого алгоритма и его алгоритмической сложности
2.0 Middle🔥 161 комментариев
#Архитектура Flutter#ООП и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Примеры алгоритмов и их алгоритмической сложности (Big O)
Алгоритмическая сложность — это анализ того, как быстро растёт время выполнения (или память) при увеличении входных данных. Это критически важно для производительности приложений.
O(1) — Константная сложность
// O(1): время не зависит от размера данных
int getFirstElement(List<int> list) {
return list[0]; // Всегда один шаг!
}
bool checkDictionary(Map<String, int> dict, String key) {
return dict.containsKey(key); // Hash lookup = O(1)
}
int findInArray(List<int> arr, int target) {
// Если массив sorted и используем binary search
// Но для случайного доступа = O(1)
return arr[5]; # Всегда один шаг
}
// Примеры O(1) операций:
// - Доступ к элементу по индексу
// - Вставка/удаление в конец массива
// - Hash map lookup/insert/delete
// - Простые операции (a + b, if, return)
O(log n) — Логарифмическая сложность
// O(log n): время логарифмически зависит от размера
// Binary Search: поиск в отсортированном массиве
int binarySearch(List<int> sortedList, int target) {
int left = 0, right = sortedList.length - 1;
while (left <= right) {
int mid = (left + right) ~/ 2;
if (sortedList[mid] == target) {
return mid; # Найден!
} else if (sortedList[mid] < target) {
left = mid + 1; # Ищем в правой половине
} else {
right = mid - 1; # Ищем в левой половине
}
}
return -1; # Не найден
}
// Сложность анализ:
// n = 1000000 → max 20 итераций (log2(1000000) ≈ 20)
// n = 1000000000 → max 30 итераций
// ОЧЕНЬ эффективно!
void main() {
final nums = [1, 3, 5, 7, 9, 11, 13, 15];
print(binarySearch(nums, 7)); # 3 → найден
print(binarySearch(nums, 10)); # -1 → не найден
}
// Примеры O(log n) операций:
// - Binary search
// - Tree operations (balanced)
// - Divide and conquer
O(n) — Линейная сложность
// O(n): время линейно зависит от размера
// Linear Search: поиск в неотсортированном массиве
int linearSearch(List<int> list, int target) {
for (int i = 0; i < list.length; i++) {
if (list[i] == target) {
return i; # Найден
}
}
return -1; # Не найден
}
// Сложность:
// n = 1000 → max 1000 итераций
// n = 1000000 → max 1000000 итераций
// Линейный рост
// Сумма элементов массива
int sumArray(List<int> arr) {
int sum = 0;
for (int num in arr) { # O(n) цикл
sum += num;
}
return sum;
}
// Примеры O(n) операций:
// - Простой цикл по массиву
// - Linear search
// - Итерация по коллекции
// - Map и filter
O(n log n) — Квазилинейная сложность
// O(n log n): очень распространено в сортировке
// Merge Sort: эффективная сортировка
List<int> mergeSort(List<int> arr) {
if (arr.length <= 1) return arr; # Base case
int mid = arr.length ~/ 2;
List<int> left = mergeSort(arr.sublist(0, mid)); # Рекурсия O(log n)
List<int> right = mergeSort(arr.sublist(mid)); # Рекурсия O(log n)
return merge(left, right); # Слияние O(n)
}
List<int> merge(List<int> left, List<int> right) {
List<int> result = [];
int i = 0, j = 0;
while (i < left.length && j < right.length) { # O(n)
if (left[i] <= right[j]) {
result.add(left[i++]);
} else {
result.add(right[j++]);
}
}
result.addAll(left.sublist(i));
result.addAll(right.sublist(j));
return result;
}
// Анализ:
// n = 8: 8 * log2(8) = 8 * 3 = 24 операций
// n = 1000: 1000 * log2(1000) ≈ 10000 операций
// Очень эффективно!
void main() {
final nums = [5, 2, 8, 1, 9];
final sorted = mergeSort(nums);
print(sorted); # [1, 2, 5, 8, 9]
}
// Примеры O(n log n) операций:
// - Merge Sort
// - Quick Sort (среднее)
// - Heap Sort
// - Tree operations (balance)
O(n²) — Квадратичная сложность
// O(n²): два вложенных цикла
// Bubble Sort: простая, но неэффективная сортировка
List<int> bubbleSort(List<int> arr) {
List<int> result = List<int>.from(arr);
for (int i = 0; i < result.length; i++) { # O(n) цикл
for (int j = 0; j < result.length - i - 1; j++) { # O(n) вложенный цикл
if (result[j] > result[j + 1]) {
// Swap
int temp = result[j];
result[j] = result[j + 1];
result[j + 1] = temp;
}
}
}
return result;
}
// Сложность:
// n = 100: 100² = 10000 операций
// n = 1000: 1000² = 1000000 операций!
// n = 10000: 10000² = 100000000 операций!!
// Очень медленно при больших данных
// Вложенный цикл без сортировки
bool hasDuplicate(List<int> arr) {
for (int i = 0; i < arr.length; i++) { # O(n)
for (int j = i + 1; j < arr.length; j++) { # O(n)
if (arr[i] == arr[j]) { # Проверка O(1)
return true;
}
}
}
return false;
}
// Примеры O(n²) операций:
// - Bubble Sort
// - Insertion Sort
// - Selection Sort
// - Вложенные циклы
O(2^n) — Экспоненциальная сложность
// O(2^n): ужасно медленно!
// Fibonacci: наивная рекурсивная реализация
int fibonacciNaive(int n) {
if (n <= 1) return n;
return fibonacciNaive(n - 1) + fibonacciNaive(n - 2);
# Каждый вызов создаёт 2 вызова
# 2^n операций!
}
// Дерево вызовов для fib(5):
// fib(5) = fib(4) + fib(3)
// = (fib(3) + fib(2)) + (fib(2) + fib(1))
// = ...
// ~32 вызова для n=5!
// ~1000000 вызовов для n=30!!
print(fibonacciNaive(5)); # 5 → быстро
print(fibonacciNaive(20)); # 6765 → медленно
print(fibonacciNaive(40)); # зависнет...
// ✅ Лучше: Динамическое программирование O(n)
int fibonacciOptimized(int n) {
if (n <= 1) return n;
List<int> dp = List<int>.filled(n + 1, 0);
dp[1] = 1;
for (int i = 2; i <= n; i++) { # O(n) цикл
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
print(fibonacciOptimized(40)); # Мгновенно!
// Примеры O(2^n) операций:
// - Наивная рекурсия
// - Subset generation
// - Permutations
// ИЗБЕГАЙ!
O(n!) — Факториальная сложность
// O(n!): еще хуже, чем экспоненциальная
// Перестановки (permutations)
List<List<int>> permutations(List<int> arr) {
if (arr.length <= 1) return [arr];
List<List<int>> result = [];
for (int i = 0; i < arr.length; i++) {
int current = arr[i];
List<int> remaining = [...arr.sublist(0, i), ...arr.sublist(i + 1)];
for (final perm in permutations(remaining)) {
result.add([current, ...perm]);
}
}
return result;
}
// Сложность:
// n = 5: 5! = 120 перестановок → быстро
// n = 10: 10! = 3628800 перестановок → медленно
// n = 12: 12! = 479001600 перестановок → очень медленно
// n = 15: 15! = ~1 триллион → невозможно
print(permutations([1, 2, 3]).length); # 6
print(permutations([1, 2, 3, 4]).length); # 24
// permutations([1, 2, 3, 4, 5]); # 120 — ещё окей
// permutations(List<int>.generate(15, (i) => i)); # НИКОГДА!
// Примеры O(n!) операций:
// - Перестановки
// - Брутфорс комбинации
// - Некоторые NP-complete задачи
// АБСОЛЮТНО ИЗБЕГАЙ!
Таблица сравнения сложностей
Сложность | n=10 | n=100 | n=1000 | n=10000
─────────────┼───────┼──────────┼───────────┼───────────
O(1) | 1 | 1 | 1 | 1
O(log n) | 3 | 7 | 10 | 13
O(n) | 10 | 100 | 1000 | 10000
O(n log n) | 30 | 700 | 10000 | 130000
O(n²) | 100 | 10000 | 1000000 | 100000000
O(2^n) | 1024 | 10^30 | 10^300 | 10^3000
O(n!) | 3M | 10^157 | 10^2567 | 10^35659
Вывод: Старайся достичь O(1), O(log n) или O(n)
Практические примеры в Flutter
// ✅ Хорошо: O(n)
class UserListViewModel {
List<User> filterUsers(List<User> users, String query) {
return users.where(
(user) => user.name.toLowerCase().contains(query.toLowerCase()),
).toList(); # O(n)
}
}
// ❌ Плохо: O(n²)
class BadUserListViewModel {
List<User> filterUsers(List<User> users, String query) {
List<User> result = [];
for (final user in users) { # O(n)
for (final char in query) { # O(m) — вложенный!
if (user.name.contains(char)) {
result.add(user);
break;
}
}
}
return result; # O(n*m)
}
}
// ✅ Хорошо: O(n)
class EfficientSearch {
List<User> searchUsers(List<User> users, String query) {
// Используй регулярное выражение или простой contains
return users
.where((u) => u.name.toLowerCase().contains(query.toLowerCase()))
.toList();
}
}
// ❌ Плохо: O(n²) для больших списков
final displayedUsers = allUsers.map((user) {
// Если в этом блоке O(n) операция — итого O(n²)!
final permissions = userPermissions.where((p) => p.userId == user.id).toList();
return UserDisplayModel(user: user, permissions: permissions);
}).toList();
// ✅ Хорошо: O(n)
final permissionMap = Map.fromIterable(
userPermissions,
key: (p) => p.userId,
value: (p) => p,
);
final displayedUsers = allUsers.map((user) {
final permissions = permissionMap[user.id] ?? [];
return UserDisplayModel(user: user, permissions: permissions);
}).toList();
Резюме
Big O сложности (от лучшей к худшей):
- O(1) — идеально (доступ по индексу, hash lookup)
- O(log n) — отлично (binary search)
- O(n) — хорошо (простой цикл)
- O(n log n) — приемлемо (merge sort)
- O(n²) — медленно (bubble sort, вложенные циклы)
- O(2^n) — очень медленно (наивная рекурсия)
- O(n!) — невозможно (перестановки)
Best Practice:
- ✅ Анализируй сложность алгоритмов
- ✅ Используй структуры данных правильно
- ✅ Избегай вложенных циклов
- ✅ Используй мемоизацию для рекурсии
- ✅ Профилируй код перед оптимизацией