English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

nu

게스트
1 / ?
수업 목록으로

절대적 비용이 아닌 성장률

Big O: 비용이 얼마나 빠르게 증가하는지 측정하기

Big O 표기법은 알고리즘의 성장률을 측정합니다 — 절대적 비용이 아닙니다.

Big O가 답하는 질문: 입력 크기 N이 두 배가 되면, 작업도 두 배가 될까? 네 배가 될까? 그대로 유지될까?

'O'는 '크기 순서'를 의미합니다. 괄호 안의 식은 성장 곡선의 모양을 설명합니다.

Big O 성장 표: O(1), O(N), O(N²)에서 N=10, 100, 1,000에서의 연산 수

주요 복잡도 클래스:

- O(1) — 상수: 비용이 증가하지 않습니다. 예: 배열에서 인덱스로 값을 조회하는 것. 배열이 10개의 항목을 가지든 1천만 개를 가지든, 한 번의 조회는 여전히 한 번의 조회입니다.

- O(N) — 선형: 비용은 입력과 함께 증가합니다. 예: 목록의 모든 항목을 한 번씩 읽는 것. 목록을 두 배로 늘리면, 읽기도 두 배가 됩니다.

- O(N²) — 제곱: 비용은 입력의 제곱으로 증가합니다. 예: 모든 항목을 다른 모든 항목과 비교하는 것. N을 두 배로 늘리면 작업은 네 배가 됩니다.

성장 비교 표:

NO(1)O(N)O(N²)
10110100
100110010,000
1,00011,0001,000,000

N=10에서 차이는 작아 보입니다. N=1,000에서 간격은 엄청나게 커집니다.

O(N)과 O(N²) 비교하기

위의 성장 비교 표를 사용하세요.

N=1,000일 때: O(N)은 1,000개의 연산이 필요합니다. O(N²)은 1,000,000개의 연산이 필요합니다.

N=1,000일 때, O(N²)은 O(N)에 비해 몇 배 더 많은 연산이 필요한가요? 계산 과정을 보여주세요.

코드 구조가 복잡도를 어떻게 드러내는가

코드에서 Big O를 식별하는 방법

Big O는 당신의 코드의 모양에 숨어있습니다. 네 가지 패턴이 대부분의 경우를 다룹니다:

N개 항목에 대한 단일 루프: O(N)

# O(N): 리스트를 한 번 통과
for item in list_of_n_items:
    process(item)

각각 N개 항목에 대한 중첩 루프: O(N²)

# O(N²): 모든 항목이 다른 모든 항목과 쌍을 이룸
for item_a in list_of_n_items:
    for item_b in list_of_n_items:
        compare(item_a, item_b)

상수 시간 조회: O(1)

배열 인덱스 액세스, 해시 테이블 조회 — 크기와 무관하게 한 단계.

분할 정복(매 단계마다 문제를 반으로 자르기): O(log N)

이진 검색: 각 확인이 남은 항목을 절반으로 줄입니다.

---

숨겨진 O(N²): 루프 안의 리스트

# 이것은 O(N)처럼 보이지만 실제로는 O(N²)입니다
visited = []
for item in list_of_n_items:
    if item not in visited:   # 방문한 모든 항목을 스캔: O(N)
        visited.append(item)  # 전체 루프: O(N²)

if item not in visited 줄이 각 반복에서 visited의 모든 요소를 스캔합니다. 리스트 스캔은 O(N)의 비용이 듭니다. N번 실행되는 루프가 각각 O(N)의 작업을 수행합니다: O(N) × O(N) = O(N²).

이 패턴은 1,000개 이상의 오픈 소스 프로젝트에 나타납니다. 해결책은 한 글자입니다: visited = []visited = set()으로 바꾸세요. Set 멤버십 테스트는 O(N)이 아닌 O(1)의 비용이 듭니다. 한 번의 변경. 같은 결과. N=1,000일 때 1,000배 더 빠름.

이 코드를 분류하고 수정하기

이 코드를 읽어보세요:

result = []
for name in student_names:
    if name not in result:
        result.append(name)
이 코드의 Big O 복잡도는 무엇인가요? `if name not in result` 줄이 비용을 높이는 이유를 설명하세요. 그 다음 이 코드를 O(N)으로 만드는 방식으로 다시 작성하세요.

이론이 실제를 만날 때

현실 속의 Big O

Big O는 단지 이론이 아닙니다. 정확히 같은 작업에서 초 단위로 끝나는 프로그램과 17분이 걸리는 프로그램을 구분합니다.

실제 예: 빌드 시스템의 의존성 순환 감지.

소프트웨어를 설치할 때, 컴퓨터는 의존성 그래프를 해결합니다: 패키지 A는 B가 필요하고, B는 C가 필요하고, 이런 식으로 계속됩니다. 빌드 시스템은 이 그래프에서 순환을 확인합니다.

O(N²) 순환 감지 알고리즘은 N=100개 패키지일 때 잘 작동합니다. N=50,000개 패키지일 때 (실제 프로젝트): 17분이 걸립니다. O(N) 해결책: 10초.

이 정확한 결함은 GHC (Haskell 컴파일러), pip (Python 패키지 관리자), Maven (Java 빌드 시스템), Cargo (Rust 패키지 관리자), & 1,000개 이상의 다른 프로젝트에 존재합니다.

그들의 저자들이 부주의해서가 아닙니다. visited = []는 N이 작을 때 작성되었습니다. 코드가 굳어졌습니다. N이 증가했습니다. 아무도 빌드가 30분이 걸릴 때까지 눈치채지 못했습니다.

Big O는 무슨 일이 일어났는지 이름을 붙일 수 있게 해주는 어휘입니다 — & 그것을 수정합니다.

---

더 깊이 있는 학습을 원하세요? 우리 unhamming 과정은 Big O에 대한 완전한 장, MOAD-0001 (1,000개 이상의 오픈 소스 프로젝트에서 발견된 실제 O(N²) 결함), & 패턴의 이름을 붙이면 어디서나 찾을 수 있는 이유를 포함합니다. 누락된 장: 알고리즘 복잡도를 참조하세요.

빌드 시간 예측하기

빌드 시스템은 O(N²) 순환 감지를 사용합니다.

측정된 빌드 시간:

- N=100개 패키지: 0.01초

- N=1,000개 패키지: 1초

O(N²)의 경우: 시간은 (N_new / N_old)²로 조정됩니다.

O(N)의 경우: 시간은 (N_new / N_old)로 조정됩니다.

이 공식들과 측정 데이터를 사용하여: (A) N=10,000에서 O(N²) 버전은 얼마나 걸릴까요? (B) O(N) 해결책 후, N=1,000을 기준으로 사용하여 N=10,000에서 O(N) 버전은 얼마나 걸릴까요? 둘 다 계산 과정을 보여주세요.