시간 복잡도
- 정의 : 실행시간과 입력값 n의 함수 관계
- n의 의미 파악 : n은 문제의 크기, 입력 데이터의 크기, 반복 횟수 등 문맥에 따라 다를 수 있으므로 정확히 이해해야 합니다.
- 빅오표기법(점근법)을 쓴다.
- 시간복잡도 vs 실행시간
시간복잡도를 통해서 실행시간을 정확하게 알 수는 없다.
O(n)
for i range(n)
print("proc") # 이 함수의 실행시간이 너무 크면 안되는 경우도 있다.
# 리스트에서 최대값을 찾는 경우
def find_max(arr):
max_value = arr[0]
n = len(arr)
for i in range(n):
if arr[i] > max_value:
max_value = arr[i]
return max_value
O(1)
a = 7
b = 34
print("hello");
input(n)
for i in range(1000):
print(n)
O(n^2)
n = 10 # N을 원하는 값으로 설정
for i in range(n):
print("hello")
for j in range(n):
print("bye")
O(n) + O(n) = O(2n) = O(n)
n = 100 # N을 원하는 값으로 설정
for i in range(N):
for j in range(N):
print("hello")
O(n) * O(n) = O(n^2)
n = 100 # N을 원하는 값으로 설정
m = 10
for i in range(N):
for j in range(m):
print("hello")
O(n) * O(m) = O(nm)
O(nm)
lst1 = [1, 2, 3, 4, 5]
lst2 = [3, 4, 5, 6, 7, 8, 10]
common = []
for i in range(len(lst1)):
for j in range(len(lst2)):
if lst1[i] == lst2[j]:
common.append(lst1[i])
# 두 리스트 예제
lst1 = [1, 2, 3, 4, 5]
lst2 = [3, 4, 5, 6, 7, 8, 10]
print(find_common_elements(lst1, lst2))
함수, 연산자, 메서드의 시간복잡도
- list
# 총 7개의 방이 존재한다고 가정
# visited는 지금까지 방문한 모든 방의 번호를 저장
visited = [0, 1, 3, 5]
if 3 in visited: # O(n)
print("room number 3 visited")
for node in visited:
print(node)
리스트에 in 연산자를 쓰는 경우
visited = [True, True, False, True, False, True, False]
# if visited[3] == True:
if visited[3]:
print("room number 3 visited")
인덱스를 통해 접근 하는 경우
- hashtable
visited = {0: True, 1: True, 3: True, 5: True}
if 3 in visited: # O(1)
print("room number 3 visited")
visited = set()
visited = {0, 1, 3, 5}
if 5 in visited: # O(1)
print("room number 5 visited")
해쉬테이블은 big O(1)으로 바로 알 수 있다. 위에 리스트에 in연산자와는 다르다. 상당히 신기하다.
-
sort() - $O(nlogn)$
-
heappush() / heappop() - $O(logn)$
-
deque
import time from collections import deque # Deque를 사용한 예제 def deque_example(): dq = deque() start_time = time.time() # 맨 앞에 100000번 요소 추가 for i in range(100000): dq.append(i) # 맨 앞에서 100000번 요소 제거 for i in range(100000): dq.popleft() end_time = time.time() print(f"Deque 작업 시간: {end_time - start_time} 초") # List를 사용한 예제 def list_example(): lst = [] start_time = time.time() # 맨 앞에 100000번 요소 추가 for i in range(100000): lst.append(i) # 맨 앞에서 100000번 요소 제거 for i in range(100000): del lst[0] end_time = time.time() print(f"List 작업 시간: {end_time - start_time} 초") # 실행 deque_example() # Deque 작업 시간: 0.012186765670776367 초 list_example() # List 작업 시간: 3.028756856918335 초
2. 시간복잡도 심화
흔히 실수하는 예시
-
for - while 중첩 되었다고 O(n^2)이 아니다.
def longestConsecutive(nums): nums = set(nums) # O(n) 중복된 숫자를 없애기 위함 answer = 0 for num in nums: # O(n) if num -1 not in nums: cnt = 1 while num + 1 in nums: num += 1 cnt += 1 answer = max(answer, cnt) return answer nums = [1,2,3,1,8,4,5,6,7,8,9,10] print(longestConsecutive(nums)) # 10
상수 시간도 완전 무시할 순 없다.
- O(V+E)
- 시간복잡도가 몇일까??
V + E이다. 이유는 각 전체 노드의 길이(V)와 + 노드당 연결되어있는 포인터들(E)만 체크하기 때문에 V + E이다. V + E가 상수 시간은 아니다.
- 시간복잡도가 몇일까??
from collections import deque
def bfs(graph, start_node):
visited = set()
queue = deque([start_node])
visited.add(start_node)
while queue:
cur_node = queue.popleft()
for next_node in graph[cur_node]:
if next_node not in visited:
queue.append(next_node)
visited.add(next_node)
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E']
}
start_node = 'A'
bfs(graph, start_node);
재귀의 시간복잡도
- 계산이 가능은 하지만, 엄밀하진 못하다.
- overhead가 크다.
- 따라서 보수적으로 계산해야한다.
백트래킹의 시간복잡도
- 계산이 불가능한 영역이 있다.
- nqueen
3. 코테 실전 적용
기준점 : 10^8
시간복잡도는 만능이 아니다.
다만 풀이에 대한 확신으로써 쓸 수 있다.
제약조건이 M x N일때, (400 x 400) 10^8보다 작았기 때문에 4중 포문으로 풀어도 가능했던 예시