-
[Algorithm] Big-O NotationAlgorithm 2024. 5. 23. 00:43
알고리즘의 중요한 개념인 Big-O 표기법에 대해 정리하였습니다.
< Big-O Notation >
Big-O 표기법(Big-O Notation)은 알고리즘의 효율성을 표현하는 방법으로, 주로 입력 크기(n)에 따른 알고리즘의 시간 복잡도와 공간 복잡도를 나타내는 데 사용됩니다. 이를 통해 알고리즘이 실행될 때 얼마나 많은 리소스를 사용하는지 대략적으로 평가할 수 있습니다.
Big-O Notation에서 상수는 항상 무시합니다.
< 시간 복잡도(Time Complexity) >
시간 복잡도(Time Complexity)는 알고리즘이 완료되는 데 걸리는 시간을 나타내는 지표로, 주어진 입력 크기(n)에 대한 연산의 수를 평가합니다.
[ O(1) : 상수 시간 복잡도]
: 입력 크기에 상관없이 항상 일정한 시간이 걸리는 경우입니다.
Ex)
function getFirstElement(arr) { return arr[0]; }
: 위 함수는 배열의 첫 번째 요소를 반환합니다. 배열의 크기와 무관하게 항상 일정한 시간이 걸리므로 O(1) 시간 복잡도를 가집니다.
[ O(log n): 로그 시간 복잡도 ]
: 입력 크기가 커질수록 시간 증가가 로그 함수와 같은 경우입니다
Ex)
function binarySearch(arr, target) { let left = 0; let right = arr.length - 1; while (left <= right) { const mid = Math.floor((left + right) / 2); if (arr[mid] === target) { return mid; } else if (arr[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return -1; }
: 이진 탐색은 정렬된 배열에서 값을 찾기 위해 중간 요소와 비교하여 범위를 절반씩 줄여 나갑니다. 따라서 O(log n) 시간 복잡도를 가집니다.
[ O(n): 선형 시간 복잡도 ]
: 입력 크기에 비례하여 시간이 증가하는 경우입니다.
Ex)
function linearSearch(arr, target) { for (let i = 0; i < arr.length; i++) { if (arr[i] === target) { return i; } } return -1; }
: 위 함수는 배열에서 특정 값을 찾기 위해 모든 요소를 검사합니다. 배열의 크기가 커질수록 반복 횟수가 증가하므로 O(n) 시간 복잡도를 가집니다.
[ O(n log n): 로그 선형 시간 복잡도 ]
: 입력 크기 n과 로그 n의 곱에 비례하는 시간 증가입니다.
Ex)
function mergeSort(arr) { if (arr.length <= 1) { return arr; } const mid = Math.floor(arr.length / 2); const left = mergeSort(arr.slice(0, mid)); const right = mergeSort(arr.slice(mid)); return merge(left, right); } function merge(left, right) { let result = []; let leftIndex = 0; let rightIndex = 0; while (leftIndex < left.length && rightIndex < right.length) { if (left[leftIndex] < right[rightIndex]) { result.push(left[leftIndex]); leftIndex++; } else { result.push(right[rightIndex]); rightIndex++; } } return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex)); }
: 병합 정렬은 배열을 반으로 나누어 각각 정렬한 후 병합합니다. 이 과정은 O(n log n) 시간 복잡도를 가집니다.
[ O(n²): 이차 시간 복잡도 ]
: 중첩된 반복문으로 인해 입력 크기의 제곱에 비례하여 시간이 증가하는 경우입니다.
Ex)
function bubbleSort(arr) { for (let i = 0; i < arr.length; i++) { for (let j = 0; j < arr.length - i - 1; j++) { if (arr[j] > arr[j + 1]) { // Swap let temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } return arr; }
: 버블 정렬은 배열을 정렬하기 위해 중첩된 반복문을 사용하므로 O(n^2) 시간 복잡도를 가집니다.
[ O(2ⁿ): 지수 시간 복잡도 ]
: 입력 크기 n에 대해 지수적으로 시간이 증가하는 경우입니다.
Ex)
function fibonacci(n) { if (n <= 1) { return n; } return fibonacci(n - 1) + fibonacci(n - 2); }
: 피보나치 수열을 재귀적으로 계산할 때, 동일한 부분 문제를 여러 번 계산하므로 O(2^n) 시간 복잡도를 가집니다.
[ O(n!): 팩토리얼 시간 복잡도 ]
: 입력 크기 의 팩토리얼에 비례하여 시간이 증가하는 경우입니다.
Ex)
function generatePermutations(arr) { if (arr.length === 0) return [[]]; const result = []; for (let i = 0; i < arr.length; i++) { const rest = generatePermutations(arr.slice(0, i).concat(arr.slice(i + 1))); for (let perm of rest) { result.push([arr[i]].concat(perm)); } } return result; }
: 주어진 배열의 모든 가능한 순열을 생성하기 위해 각 요소에 대해 재귀적으로 순열을 계산하므로 O(n!) 시간 복잡도를 가집니다.
< 시간 복잡도(Time Complexity) >
공간 복잡도는 알고리즘이 완료되는 데 필요한 메모리 공간을 나타냅니다. 시간 복잡도와 마찬가지로 입력 크기 n에 대한 함수로 표현됩니다. 예를 들어, 배열을 사용하는 경우 O(n)의 공간 복잡도를 가질 수 있습니다.
Ex1 : 선형 검색(Linear Search)
def linear_search(arr, target): for i in range(len(arr)): if arr[i] == target: return i return -1
- 시간 복잡도: O(n)
- 공간 복잡도: O(1)
Ex2 : 병합 정렬(Merge Sort)def merge_sort(arr): if len(arr) > 1: mid = len(arr) // 2 left_half = arr[:mid] right_half = arr[mid:] merge_sort(left_half) merge_sort(right_half) i = j = k = 0 while i < len(left_half) and j < len(right_half): if left_half[i] < right_half[j]: arr[k] = left_half[i] i += 1 else: arr[k] = right_half[j] j += 1 k += 1 while i < len(left_half): arr[k] = left_half[i] i += 1 k += 1 while j < len(right_half): arr[k] = right_half[j] j += 1 k += 1
- 시간 복잡도: O(n log n)
- 공간 복잡도: O(n)
'Algorithm' 카테고리의 다른 글
[Algorithm] 정렬 (0) 2024.05.19