2. 시간 복잡도

너스레:)·2023년 7월 24일
0

Algorithm-Python

목록 보기
2/2
post-thumbnail

2.1. 시간 복잡도란?

  • 더 나은 알고리즘을 판단하는 기준

  • 복잡도
    - 시간 복잡도
    - 공간 복잡도

가장 먼저, 제시된 제한 시간메모리 사용량을 확인한 후에, 조건에 맞게 실행되도록 문제를 풀어야 한다.

프로그래머스의 경우, 제한시간이 일반적으로 10초임.

2.1.1. 빅오(Big-O) 표기법

  • Big-O 표기법 : 점근 표기법 中 점근적 상한 표기법
  • 결국엔 최고차항만 남는다
    - 상대적으로 나머지 연산은 너무 작아 무시 가능

2.1.2. 시간 복잡도의 그래프

1 < log(n) < n < nlog(n) < n^2 < 2^n < n!

  • 컴퓨터는 대략 1초에 1억 번 연산한다.
    - Why?
    ☞ CPU가 1초에 1억 번 연산하기 때문.
    ☞ 만약 컴퓨터나 CPU의 성능이 여기서 더 좋아진다고 하더라도, 요즘 출제는 근본적인 알고리즘 자체가 틀리면 아무리 코드를 최적화한다고 해도 통과할 수 없도록 출제하고 있음.

따라서 입력 데이터와 연산 횟수를 미리 고려해서, 시간 제한을 넘기지 않는 알고리즘을 처음부터 잘 선택하는 것이 중요!

cf) 각 시간 복잡도의 개념 & 사용되는 상황

2.1.3. 시간 복잡도 선택 시 참고사항

시간 복잡도를 먼저 고려하여, 불가능한 시간 복잡도의 방법은 과감하게 배제하는 식으로 풀이시간 줄일 것.

ⓐ 입력 데이터 수에 따른 시간 복잡도 (기준: 1초)

  • 1천 개 ☞ O(n^2) 이하
  • 1만 개 ☞ O(n^2) 미만
  • 10만 개 ☞ O(nlog(n)) 이하
  • 100만 개 ☞ O(nlog(n)) 미만
  • 그 이상 ☞ 아예 특정 알고리즘을 사용하도록 요구하는 문제일 가능성 ↑. 문제 조건 유심히 확인할 것.

ⓑ 자료구조에 따른 시간 복잡도

자료구조탐색삽입삭제
배열O(n)O(n)O(n)
정렬된 배열O(log(n))O(n)O(n)
연결 리스트O(n)O(1)O(1)
스택/큐O(n)O(1)O(1)
해시O(1)O(1)O(1)
이진 트리O(log(n))O(log(n))O(log(n))

2.2. 시간 복잡도의 계산

2.2.1. 시간 복잡도_어림짐작 해보기

계산이 고정된 횟수 이내로 끝난다면, 반복문이나 좀 복잡한 연산을 했더라도 섣부르게 O(n^2)라고 단정 지을 필요 없음.
즉, 입력 데이터 수에 따라 정말 해당 시간 복잡도가 나오는 것이 맞나 체크해볼 것.

빅오 표기법
☞ 시간 복잡도에 대해 빠르게 감을 잡기 위함이지, 정확하게 측정하고자 함이 아니다.

cf) 내장 함수의 시간 복잡도

자주 사용하는 내장 함수 (3가지)
: 리스트, 집합, 딕셔너리

https://wiki.python.org/moin/TimeComplexity

가장 오래 걸리는 작업 또는 라이브러리 함수의 시간 복잡도를 우선 계산하고,
추가로 연산이 발생하는 곳을 탐색하는 방향으로
전체 시간 복잡도에 대한 감을 잡는 것이 좋다.

2.2.2. 시간 복잡도_줄이는 방법

ⓐ readline() 함수 ☞ '입력 속도' 줄이기

import sys

data = sys.stdin.readline()
  • 입력값을 받는 코드도 직접 구현을 해야하는 경우
  • 받은 문자열을 다시 쪼개더라도, input() 함수보다는 sys 모듈의 readline() 함수를 이용하는 게 훨씬 효율적이다!

ⓑ 리스트 곱셈 ☞ 빠른 '초기화 & 할당'

data1 = [0 for _ in range(1000)]	# O(n)

data2 = [0] * 1000					# O(nk) // 권장
  • 미리 할당해놓은 '고정' 배열을 다뤄야하는 경우
    (보통 '리스트'는 '동적'으로 다루는 게 일반적이긴 함)

ⓒ 문자열 붙이기 ☞ YES : ''.join() / NOPE : +

Q. 왜 문자열들을 붙일 때, +를 사용하면 안 되는가?
A. 파이썬에서 문자열(str)은 'immutable'이기 때문이다.
     따라서 +을 사용할 경우, 각각의 문자열을 새로운 메모리에 복사해서 새 문자열을 만들기 때문에, 사실상 그 시간 복잡도가 O(n^2)이 되어버린다.

그러니까 문자열을 붙이고 싶다면, ''.join() 을 사용하자!

immutable 이란?
An object with fixed value.
Immutable objects include numbers, strings and tuples.
Such an object cannot be altered. A new object has to be created if a different value has to be stored. They play an important role in places where a constant hash value is needed such as the keys of a dictionary.

ⓓ 조건문의 연산 줄이기 ☞ '더 짧은' 조건을 '더 앞쪽에' 배치

다중 조건의 경우,

  • and ☞ 앞쪽 조건이 False라면
  • or ☞ 앞쪽 조건이 True라면

그 뒤쪽 조건은 계산하지 않고 바로 결과를 도출한다.

ⓔ 슬라이싱 ☞ 불필요한 연산 줄이기

  • 파이썬의 특별한 기능
  • 슬라이싱을 통해, 하나의 변수를 최대한 활용하여 많은 불필요한 코드를 줄일 수 있음
  • Where to 적용? ☞ 연속된(iterable) 자료형 ; 리스트, 튜플, 문자열 등
  • for 문에도 활용 가능
  • 주의할 점 : a[start : end : step] 에서 end는 포함 X
  • '음수' 인덱스
data = [[(0, 1), (2, 3), (4, 5)], [(6, 7), (8, 9), (0, 1)]]
print(data[1:][0][::2])

'''
결과값

[(6, 7), (0, 1)]
'''

'리스트'를 슬라이싱 했다면, 결과값도 '리스트' 형태로 나옴

ⓕ 표준 라이브러리의 활용 ☞ 안전성 & 속도 모두 잡기

  • 어떤 언어를 사용하든 간에, 언어별 기본 특성 & 유용한 라이브러리는 반드시 파악해둘 것
  • 지금 이 코드를 '직접' 구현하는 게 정말 맞나;
    즉, 이미 검증을 마친 '표준 라이브러리'에 손쉽게 맡길 순 없는가 다시 한 번 생각해볼 것
  • 표준이 아닌 '외부' 라이브러리의 사용은 지양

# 자주 사용하는 표준 라이브러리

  1. heapq

    • 이진 트리 기반의 최소 힙 자료구조
    • 항상 정렬된 상태에서 값 추가/삭제
    • 우선순위 큐, 최단 거리 알고리즘
  2. collections

    • counter
      - 연속된 자료형에서 동일한 원소가 몇 개 있는지 확인 가능
      - 해시 문제
    • deque
  3. itertools

    • 경우의 수 문제
      - 순열(permutations), 조합(combinations), 중복순열(permutations_with_replacement), 중복조합(combinations_with_replacement)
  4. math

    • 수학 문제
      - 최대공약수, 최소공배수, 팩토리얼, 제곱근, 로그, 상수(파이, 자연상수) 등
  5. bisect

    • 이진 탐색 기능
      - 정렬된 데이터의 특정 범위 안에 원소가 있는지, 몇 개가 존재하는지 확인

ⓖ (리스트) 컴프리헨션 vs. 제너레이터 ☞ 편의성 vs. 효율성

# (리스트) 컴프리헨션

<장점>

  • [ 선언 → 할당 → 재구성 ]의 과정을 단 한 줄에 모두 끝낼 수 있는 편의성
  • 적용가능한 자료형 : 리스트(多) + tuple, set, dict 등
  • if 문의 중첩 ⇒ "and" 로 취급함
[i for i in range(11) if i % 2 == 0 if i % 5 == 0]

<단점>

  • 공간 복잡도의 고려 必
    - 컴프리헨션은 데이터가 10만 개, 100만 개 있을 때, 정말 그 데이터들을 모두 생성하기 때문
    - 편리한 기능은 대부분 공간 복잡도의 문제가 발생하는 경우가 많음..
  • 너무 과도한 컴프리헨션은 이해를 어렵게 해 오히려 큰 문제를 일으킴

# 제너레이터

Q. '제너레이터'란?
A. 연속 가능한(iterator) 자료형을 반환하는 함수.

  • '한 번에 함수 모두 실행 & return으로 결과값 반환' (X)
  • '함수가 실행되었다가 다음 next()를 대기하면서 실행이 멈춤 & yield로 결과값 반환' (O)

cf) 제너레이터 만드는 방법 (2가지)
i. 함수 정의 시, return 대신 'yield' 작성
ii. 컴프리헨션 문구를 '소괄호 ()'로 감싸줌

<장점>

  • 엄청난 메모리 절약 (☞ 공간 복잡도의 효율성)
    - 함수가 실행할 것이 매우 많아도 메모리는 항상 고정된 값을 지님

<단점>

  • 결과 정보를 저장하기 위해서는, [ 실행 비용 + 변수 사용 ]의 불편함
    - 실행 전까지는 데이터를 갖지 않기 때문

ⓗ 데이터 돌려쓰기 ☞ 중복/연속되는 논리 줄이기

  • 중복/연속되는 논리를 한 번에 합쳐 사용하여 코드의 길이와 불필요한 연산 및 실수를 줄이는 데 집중 必

  • 논리의 핵심은 유지하되, 나의 코드를 개선할 점이 있는지 점검 必
    - 필요하지 않은 변수를 할당하진 않았나
    - 의미 없는 데이터의 복사나 연산이 발생하진 않았나

2.2.3. 여러 상황에서의 시간 복잡도 생각해보기

ⓐ 반복문

  • 함수별로 걸리는 시간 복잡도 예측
    → 가장 오래 걸리는 함수를 최적화할지, 아니면 다른 함수의 논리를 변경해 작업량을 개선할지 선택

ⓑ 차수의 계수 -- 생략 X

  • 여러 시간 복잡도가 엮이면 시간 복잡도의 빅오 표기법이 프로그램의 실제 실행시간을 예측하지 못하는 상황이 자주 발생한다.
    따라서 차수의 계수까지 모두 계산하여 고려하는 것을 권장함.

ⓒ 크기 N과 크기 M의 시간 복잡도

for i in N:
	for j in M:
    		...
  • M이 매우 작은 숫자일 경우 (~20)
    ☞ O(n)
  • M이 어느 정도 큰 숫자일 경우 (~100)
    ☞ O(nm)
  • M이 N과 거의 비슷하거나 엄청 큰 숫자일 경우
    ☞ O(n^2)
profile
💻 (CSE) Computer Science and Engineering

0개의 댓글