자료구조와 알고리즘, 시간복잡도 대해 공부한 것들을 작성한 글입니다.
알고리즘은 컴퓨터 과학에서 중요한 개념 중 하나로, 문제를 해결하기 위한 방법을 의미한다. 입력 데이터를 처리하여 원하는 결과를 생성하는 데 필요한 지침을 제공하며, 주어진 문제를 해결하는 방법을 명확하게 정의한다.
알고리즘의 효율성은 알고리즘이 입력 데이터에 대해 얼마나 빠르게 동작하는지와 밀접한 관련이 있으며, 이와 관련된 주요 개념 중 하나가 시간복잡도이다.
알고리즘에서는 문제를 해결하는 것뿐만 아니라 효율적인 방법으로 문제를 해결하는 것도 중요하다. 다시말해 시간복잡도를 고려해야한다.
시간복잡도는 문제를 해결하는데 걸리는 시간과 입력의 함수 관계를 말한다.
입력 크기가 증가하더라도 문제를 해결하는 데 걸리는 시간이 선형적으로 또는 그 이하로 증가하도록 알고리즘이 구성되어야 한다.
시간 복잡도를 나타내기 위한 표기 방법으로는 크게 3가지가 있다.
이 세가지 중 주로 Big-O 표기법을 사용한다. 이 표기법은 주어진 알고리즘이 얼마나 빨리 실행되는지를 상한으로 제한하여 표현한다.
최선의 경우인 Big-Ω 표기법이나 평균의 경우인 Big-θ 표기법을 사용하지 않고 왜 Big-O 표기법이 널리 사용되는걸까?
왜냐하면 최악의 경우에 대한 보장이 가능하기 때문이다. Big-O 표기법은 알고리즘이 어떤 입력에 대해서도 절대로 더 느리게 실행되지 않을 것임을 보장한다. 따라서 안정성을 보장하고 예측 가능한 성능을 제공하는 데 도움이 된다. 가장 비관적인 상황을 고려하기 때문에 예외적인 경우를 고려하여 시스템을 설계하고 최악의 시나리오에 대비하는 데 도움이 된다.
def firt_element(arr):
print(arr[0])
## 입력 크기와 상관없이 항상 1번의 연산만 필요
def linear_search(arr, target):
for i in range(len(arr)):
if arr[i] == target:
return i
## 입력 크기에 직선적으로 비례하여 연산이 필요
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left<= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
## 입력 크기에 대해 로그 시간 복잡도를 가짐
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
# 입력 크기의 제곱에 비례하는 연산이 필요
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) 복잡도를 가짐
def fibonacci_recursive(n):
if n <= 0:
return 0
elif n == 1:
return 1
else:
return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)
# 입력 크기에 기하급수적인 복잡도를 가짐