June

2-2) 알고리즘 분석 정리

2. Sorting (insertion, merge)

Growth of functions

개념

알고리즘의 효율성을 분석할 때, 입력 크기 n이 커질수록 실행 시간이 어떻게 증가하는지를 수학적으로 표현하는 방법이다.

Asymptotic upper bound

f(n) = O(g(n)) ⟺ ∃ c > 0, n₀ > 0 such that 0 ≤ f(n) ≤ c·g(n)

for all n ≥ n₀

정의: f(n) = O(g(n))은 충분히 큰 n에 대해 f(n) ≤ c·g(n)을 만족하는 상수 c > 0가 존재함을 의미 O-notation은 최악의 경우(worst case)에 필요한 연산 횟수를 설명하는데 사용되며, 상수와 계수를 숨겨서 알고리즘의 본질적인 성장 패턴만을 표현한다.

Asymptotic lower bound

f(n) = Ω(g(n)) ⟺ ∃ c > 0, n₀ > 0 such that f(n) ≥ c·g(n)

for all n ≥ n₀

정의: f(n) = Ω(g(n))은 충분히 큰 n에 대해 f(n) ≥ c·g(n)을 만족하는 상수 c > 0, n₀ > 0가 존재함을 의미

Ω-notation은 최선의 경우(best case)에 필요한 연산 횟수 또는 문제 자체의 복잡도 하한을 설명하는데 사용되며, 알고리즘이 최소한 수행해야 하는 연산의 수를 나타낸다.

Asymptotically tight bound

f(n) = Θ(g(n)) ⟺ ∃ c₁, c₂ > 0, n₀ > 0 such that 0 ≤ c₁·g(n) ≤ f(n) ≤ c₂·g(n)

for all n ≥ n₀

정의: f(n) = Θ(g(n))은 충분히 큰 n에 대해 c₁·g(n) ≤ f(n) ≤ c₂·g(n)을 만족하는 상수 c₁, c₂ > 0, n₀ > 0가 존재함을 의미

Θ-notation은 상한과 하한을 동시에 제공하여 정확한 성장률을 표현하며, 알고리즘의 시간 복잡도를 가장 정밀하게 나타낸다.

ex) insertion sort의 상한과 하한

Worst Case (역순 배열):

  • j번째 원소는 j번 비교 필요
  • 총 비교 횟수: 1 + 2 + ... + (n-1) = n(n-1)/2 = O(n²)

Best Case (이미 정렬된 배열):

  • 각 원소마다 단 1번만 비교 (while 조건 확인)
  • 총 비교 횟수: n-1 = Ω(n)

Insertion Sort

동작 원리

Insertion Sort는 카드 정렬과 같은 방식으로 동작합니다:

  1. 두 번째 원소부터 시작 (첫 번째는 이미 정렬된 것으로 간주)
  2. 현재 원소(key)를 선택
  3. 정렬된 부분을 뒤에서부터 탐색하며 key보다 큰 원소들을 오른쪽으로 한 칸씩 이동
  4. 적절한 위치를 찾으면 key 삽입
  5. 모든 원소에 대해 반복

예시: [5, 2, 4, 6, 1, 3]

  • Step 1: [5, |2, 4, 6, 1, 3] → key=2,
    • 5를 오른쪽으로 → [2, 5, |4, 6, 1, 3]
  • Step 2: [2, 5, |4, 6, 1, 3] → key=4,
    • 5를 오른쪽으로 → [2, 4, 5, |6, 1, 3]
  • Step 3: [2, 4, 5, |6, 1, 3] → key=6,
    • 이동 없음 → [2, 4, 5, 6, |1, 3]
  • Step 4: [2, 4, 5, 6, |1, 3] → key=1,
    • 모두 이동 → [1, 2, 4, 5, 6, |3]
  • Step 5: [1, 2, 4, 5, 6, |3] → key=3,
    • 4,5,6 이동 → [1, 2, 3, 4, 5, 6]

psudocode

c++
1  for j = 2 to A.length           // 두 번째 원소부터 시작
2      key = A[j]                   // 현재 원소를 key로 저장
3      i = j - 1                    // 정렬된 부분의 마지막 인덱스
4      while i > 0 and A[i] > key   // key보다 큰 원소 찾기
5          A[i+1] = A[i]            // 오른쪽으로 이동
6          i = i - 1                // 왼쪽으로 계속 탐색
7      A[i+1] = key                 // 찾은 위치에 key 삽입

Upper Bound (상한): O(n²) Average Case: Θ : (n²) Lower Bound (하한): Ω(n)

Bubble Sort

(자세히 안다룸)

Merge Sort

기본 개념

Merge Sort는 분할 정복(Divide and Conquer) 패러다임을 사용하는 정렬 알고리즘이다. 배열을 계속 반으로 나누어 각각을 정렬한 후, 정렬된 부분 배열들을 합병하여 전체를 정렬한다.

분할 정복 접근법

세 단계로 구성:

  1. Divide: 문제를 더 작은 하위 문제들로 분할
  2. Conquer: 하위 문제들을 재귀적으로 해결 (충분히 작으면 직접 해결)
  3. Combine: 하위 문제의 해답을 결합하여 원래 문제 해결

Merge sort 동작 원리

분할 단계

  • n개 원소의 배열을 n/2개씩 두 부분 배열로 분할
  • 각 부분 배열을 재귀적으로 merge sort로 정렬
  • 원소가 1개가 될 때까지 계속 분할 (base case)

합병 단계

  • 두 개의 정렬된 부분 배열을 하나의 정렬된 배열로 합병
  • MERGE procedure를 사용하여 효율적으로 합병

Upper Bound (상한): O(n log n) Average Case: Θ(n log n) Lower Bound (하한): Ω(n log n)

3. Sorting(heap, PQ)

Heap 자료구조

기본 개념

Heap은 Complete Binary Tree 형태의 자료구조로, 부모-자식 간 대소 관계가 정의되어 있다.

Heap 속성

Max-Heap Property:

A[PARENT(i)] ≥ A[i]

부모 노드의 값이 항상 자식 노드의 값보다 크거나 같다.

Min-Heap Property:

A[PARENT(i)] ≤ A[i]

부모 노드의 값이 항상 자식 노드의 값보다 작거나 같다.

배열로 표현

Complete Binary Tree는 배열로 효율적으로 표현 가능:

  • 루트 노드: A[1] (인덱스 1부터 시작)
  • 부모 노드: PARENT(i) = ⌊i/2⌋
  • 왼쪽 자식: LEFT(i) = 2i
  • 오른쪽 자식: RIGHT(i) = 2i + 1

MAX-HEAPIFY

동작 원리

MAX-HEAPIFY는 특정 노드에서 Max-Heap 속성을 복원하는 프로시저다:

  1. 현재 노드와 두 자식 중 가장 큰 값 찾기
  2. 현재 노드가 가장 크지 않으면 가장 큰 자식과 교환
  3. 교환된 자식 노드에 대해 재귀적으로 MAX-HEAPIFY 수행

시간 복잡도

  • 최악의 경우: 트리의 높이만큼 재귀 호출
  • 복잡도: O(log n) 또는 O(h), 여기서 h는 트리의 높이

Pseudocode

c++
1  l = LEFT(i)           // 왼쪽 자식 인덱스
2  r = RIGHT(i)          // 오른쪽 자식 인덱스
3  if l ≤ A.heap-size and A[l] > A[i]
4      largest = l
5  else largest = i
6  if r ≤ A.heap-size and A[r] > A[largest]
7      largest = r
8  if largest ≠ i        // 자식이 더 크면
9      exchange A[i] with A[largest]
10     MAX-HEAPIFY(A, largest)  // 재귀 호출

BUILD-MAX-HEAP

동작 원리

배열을 Max-Heap으로 만드는 프로시저:

  1. Bottom-up 방식으로 진행
  2. 리프 노드는 이미 heap 속성 만족 (자식이 없음)
  3. ⌊n/2⌋부터 1까지 역순으로 MAX-HEAPIFY 호출

Pseudocode

c++
1  A.heap-size = A.length
2  for i = ⌊A.length/2⌋ downto 1
3      MAX-HEAPIFY(A, i)

시간 복잡도 분석

  • 단순 분석: n/2개 노드에 대해 O(log n) → O(n log n)
  • 정밀 분석:
    • 높이 h인 노드는 최대 ⌈n/2^(h+1)⌉개
    • 각 노드의 MAX-HEAPIFY는 O(h)
    • 총 시간: Σ(h=0 to log n) ⌈n/2^(h+1)⌉ · O(h) = O(n)

Heapsort Algorithm

동작 원리

Heapsort는 Max-Heap의 속성을 이용한 정렬 알고리즘:

  1. BUILD-MAX-HEAP으로 배열을 heap으로 만듦 (최대값이 루트에)
  2. 루트(최대값)와 마지막 원소 교환
  3. Heap 크기를 1 감소시킴
  4. 새로운 루트에 대해 MAX-HEAPIFY 수행
  5. Heap 크기가 1이 될 때까지 2-4 반복

Heapsort

min : Ω(n log n)

avg : Θ(n log n)

max : O(n log n)

Priority Queue

기본 개념

Priority Queue는 각 원소가 우선순위를 가지는 자료구조로, 우선순위가 가장 높은 원소를 빠르게 접근하고 제거할 수 있다.

1. Max-Priority Queue

  • 최대값이 가장 높은 우선순위
  • Max-Heap으로 구현

2. Min-Priority Queue

  • 최소값이 가장 높은 우선순위
  • Min-Heap으로 구현

4. Sorting(quick)

개념

Quicksort는 Divide and Conquer 전략을 사용하는 효율적인 정렬 알고리즘이다. Merge Sort와 달리 In-place 정렬이 가능하며, 평균적으로 매우 빠른 성능을 보인다.

1. Divide (분할)

  • Pivot 원소를 선택
  • 배열을 pivot을 기준으로 두 부분으로 분할:
    • 왼쪽: pivot보다 작거나 같은 원소들
    • 오른쪽: pivot보다 큰 원소들
  • PARTITION 함수가 이 작업 수행

2. Conquer (정복)

  • 두 부분 배열을 재귀적으로 정렬
  • Base case: 원소가 1개 이하면 이미 정렬됨

3. Combine (결합)

  • 추가 작업 불필요!
  • Partition이 이미 올바른 위치에 배치했기 때문

5. Sorting…

Decision Tree Model

기본 개념

Decision Tree는 비교 기반 정렬 알고리즘을 추상화한 모델이다.

특징

  • 비교 기반 정렬의 추상화: 모든 comparison sort를 표현
  • 특정 크기 입력에 대한 비교: n개 원소에 대한 비교 과정
  • 비교만 카운팅: 다른 연산은 무시하고 비교 횟수만 분석

Counting Sort

Counting Sort는 각 원소가 몇 개 있는지 세어서(counting) 정렬하는 알고리즘이다.

동작 원리

  • 각 값이 몇 번 나타나는지 카운트
  • 카운트를 이용해 각 원소의 최종 위치 계산
  • 올바른 위치에 원소 배치

psudocode

c++
COUNTING-SORT(A, B, n, k)

1  for i = 0 to k
2      C[i] = 0                      // 카운트 초기화
3  for j = 1 to n
4      C[A[j]] = C[A[j]] + 1         // 각 값의 개수 카운트
5  // C[i]는 이제 값 i의 개수
6  for i = 1 to k
7      C[i] = C[i] + C[i-1]          // 누적 합 계산
8  // C[i]는 이제 i 이하 원소의 개수
9  for j = n downto 1         // 역순으로 처리 (안정성)
10     B[C[A[j]]] = A[j]              // 올바른 위치에 배치
11     C[A[j]] = C[A[j]] - 1          // 카운트 감소

Counting sort is stable

Θ(k + n), which is Θ(n) if k = O(n)

Radix Sort

Radix Sort는 숫자나 문자열을 자릿수(digit)별로 정렬하는 알고리즘이다.

동작 원리

  1. 가장 오른쪽 자릿수(1의 자리)부터 시작
  2. 각 자릿수마다 안정 정렬 수행 (보통 Counting Sort)
  3. 왼쪽으로 이동하며 반복
  4. 가장 왼쪽 자릿수까지 완료

psudocode

c++
RADIX-SORT(A, d)

1  for i = 1 to d
2      use a stable sort to sort array A on digit i (counting sort)

n: 원소 개수 d: 자릿수 k: 각 자릿수의 범위 (0~k-1)

시간 복잡도 : = Θ(d(n + k))

If k = O(n), time = Θ(dn).

6. Sorting Networks

Comparator

O(1) time.

왼쪽에서 오른쪽으로, 작은 값을 위로, 큰 값을 아래로

Depth

병렬로 연산 가능한 comparator 묶음

예시 : depth = 3

Bitonic Sorter Depth

D(n) = D(n/2) + 1

D(2) = 1

D(n) = logn

25 중간고사

1.

(a) Time Complexity

bestworstavg
QuickSort
InsertionSort
MergeSort
HeapSort

(b) best or worst

QuickSortInsertion
1234567
7654321
1326574

(c) Build-max-heap time complxity?

2. Quick Sort Psudocode 빈칸

(a)

c++
PARTITION(A, p, r)
1  x = A[r]              // 마지막 원소를 pivot으로 선택
2  i = _____             // 작은 원소들의 경계
3  for j = __ to _____  
4      if A[j] ≤ x       
5          i = _____     
6          exchange A[i] <-> ___ 
7  exchange _____ <-> A[r]  
8  return i + 1 

(b)

7, 4, 9, 5, 8, 3, 6 → partition 1번 돌린 뒤 결과 배열?

3. Counting Sort 과정

(a) 2,2,5,3,1,1,4 카운팅 소트하기, H, T 채우기

숫자를 센 뒤 H배열
최종 H 배열
최종 D 배열

4. T/F and proof

5. Sorting Network

9. Binary Search Trees

1. 개요

정의

  • 이진 탐색 트리는 연결 자료구조(linked data structure) 로 표현되며,

    각 노드는 하나의 객체이다.

노드 구조

  • root[T] : 트리 T의 루트를 가리킴

각 노드가 포함하는 필드

  • key : 키 값 (+ 위성 데이터)
  • left : 왼쪽 자식을 가리키는 포인터
  • right : 오른쪽 자식을 가리키는 포인터
  • p : 부모를 가리키는 포인터 (p[root[T]] = NIL)

2. 이진 탐색 트리의 속성 (Binary-Search-Tree Property)

저장된 키는 반드시 다음 속성을 만족해야 한다.

  • 왼쪽 서브트리
    • y가 x의 왼쪽 서브트리에 있으면
    • key[y] ≤ key[x]
  • 오른쪽 서브트리
    • y가 x의 오른쪽 서브트리에 있으면
    • key[y] ≥ key[x]

예시 트리

        5
       / \
      3   7
     / \ / \
    2  4 6  8

3. 중위 순회 (Inorder Tree Walk)

개념

  • 이진 탐색 트리 속성 덕분에
  • 중위 순회 → 키를 오름차순으로 출력

알고리즘

INORDER-TREE-WALK(x)
    ifx ≠ NIL
INORDER-TREE-WALK(left[x])
        print key[x]
INORDER-TREE-WALK(right[x])

분석

  • 정확성
    • BST 속성에 의해 귀납적으로 증명 가능
  • 시간복잡도
    • Θ(n)
    • n개의 노드를 각각 한 번씩 방문

4. 탐색 (Searching)

재귀 버전

TREE-SEARCH(x, k)
    if x= NILor k= key[x]
return x
    if k< key[x]
return TREE-SEARCH(left[x], k)
else
return TREE-SEARCH(right[x], k)

반복문 버전

ITERATIVE-TREE-SEARCH(x, k)
    while x ≠ NILand k ≠ key[x]
        if k< key[x]
            x=left[x]
else
            x=right[x]
return x

시간복잡도

  • O(h)
  • h = 트리의 높이
  • 루트에서 아래로 내려가는 경로만 방문

5. 최솟값과 최댓값 (Minimum and Maximum)

성질

  • 최솟값 : 가장 왼쪽 노드
  • 최댓값 : 가장 오른쪽 노드

알고리즘

TREE-MINIMUM(x)
while left[x] ≠NIL
x= left[x]
return x
TREE-MAXIMUM(x)
while right[x] ≠NIL
x= right[x]
return x

시간복잡도

  • O(h)
  • 루트에서 리프까지 하향 경로 방문

6. 후속자와 선행자 (Successor & Predecessor)

정의

  • 후속자 (Successor)
    • key[x]보다 큰 키 중 가장 작은 키
  • 선행자 (Predecessor)
    • key[x]보다 작은 키 중 가장 큰 키

후속자 찾는 경우

Case 1. 오른쪽 서브트리가 존재

  • 후속자 = 오른쪽 서브트리의 최솟값

Case 2. 오른쪽 서브트리가 없음

  • 위로 올라가며
  • 왼쪽 자식으로 올라가는 첫 번째 조상

알고리즘

TREE-SUCCESSOR(x)
if right[x] ≠ NIL
return TREE-MINIMUM(right[x])
    y = p[x]
while y ≠ NILandx= right[y]
        x =y
y= p[y]
return y

시간복잡도

  • O(h)

7. 삽입 (Insertion)

알고리즘

TREE-INSERT(T, z)
    y= NIL
    x= root[T]
    while x ≠ NIL
        y= x
        if key[z]< key[x]
            x=left[x]
else
            x=right[x]
    p[z]= y
    if y= NIL
        root[T]= z
else if key[z]< key[y]
left[y]= z
else
right[y]= z

동작 원리

  • 루트부터 하향 이동
  • x : 현재 위치
  • y : x의 부모 (trailing pointer)
  • x = NIL 위치에 새 노드 삽입

시간복잡도

  • O(h)

8. 삭제 (Deletion)

경우 분류

Case 1. 자식이 없는 경우 (리프)

  • 부모가 NIL을 가리키게 함

Case 2. 자식이 하나인 경우

  • 부모가 자식을 직접 가리키게 함

Case 3. 자식이 둘인 경우

  • z의 후속자 y 선택
  • y는 왼쪽 자식 없음
  • y 제거 후
  • z의 데이터 ← y의 데이터 복사

알고리즘

c++
TREE-DELETE(T, z)
    ifleft[z]= NILorright[z]= NIL
        y= z
else
        y= TREE-SUCCESSOR(z)

    ifleft[y] ≠ NIL
        x=left[y]
else
        x=right[y]

    if x ≠ NIL
        p[x]= p[y]

    if p[y]= NIL
        root[T]= x
else if y=left[p[y]]
left[p[y]]= x
else
right[p[y]]= x

    if y ≠ z
        key[z]= key[y]

return y

시간복잡도

  • O(h)

9. 시간복잡도 요약

연산시간복잡도
탐색 (Search)O(h)
최솟값 / 최댓값O(h)
후속자 / 선행자O(h)
삽입 (Insert)O(h)
삭제 (Delete)O(h)
중위 순회Θ(n)
  • h = 트리 높이

10. 높이와 균형

문제점

  • 최악의 경우
    • 트리가 한쪽으로 치우침
    • h = Θ(n)
    • 연결 리스트와 동일

편향 트리 예

1
 \
  2
   \
    3
     \
      4

해결책 : 균형 트리

  • 높이 h = O(log n) 보장
  • 삽입 / 삭제 시 재구조화 필요
  • 예 : AVL 트리, 레드-블랙 트리

성능 비교

연산일반 BST균형 BST
탐색O(n)O(log n)
삽입O(n)O(log n)
삭제O(n)O(log n)

11. C++ 구현 예시

c++
#include<iostream>
usingnamespace std;

structNode {
int key;
    Node* left;
    Node* right;
    Node* parent;

Node(int k) :key(k),left(nullptr),right(nullptr),parent(nullptr) {}
};

classBST {
private:
    Node* root;

public:
BST() :root(nullptr) {}

// 탐색
Node* search(int k) {
        Node* x = root;
while (x !=nullptr && k != x->key) {
if (k < x->key)
                x = x->left;
else
                x = x->right;
        }
return x;
    }

// 최솟값
Node* minimum(Node* x) {
while (x->left !=nullptr)
            x = x->left;
return x;
    }

// 최댓값
Node* maximum(Node* x) {
while (x->right !=nullptr)
            x = x->right;
return x;
    }

// 후속자
Node* successor(Node* x) {
if (x->right !=nullptr)
returnminimum(x->right);

        Node* y = x->parent;
while (y !=nullptr && x == y->right) {
            x = y;
            y = y->parent;
        }
return y;
    }

// 삽입
voidinsert(int k) {
        Node* z =newNode(k);
        Node* y =nullptr;
        Node* x = root;

while (x !=nullptr) {
            y = x;
if (z->key < x->key)
                x = x->left;
else
                x = x->right;
        }

        z->parent = y;

if (y ==nullptr)
            root = z;
elseif (z->key < y->key)
            y->left = z;
else
            y->right = z;
    }

// 중위 순회
voidinorder(Node* x) {
if (x !=nullptr) {
inorder(x->left);
            cout << x->key <<" ";
inorder(x->right);
        }
    }

voidinorder() {
inorder(root);
        cout << endl;
    }

Node* getRoot() {return root; }
};

intmain() {
    BST tree;

    tree.insert(5);
    tree.insert(3);
    tree.insert(7);
    tree.insert(2);
    tree.insert(4);
    tree.insert(6);
    tree.insert(8);

    cout <<"Inorder: ";
    tree.inorder();// 2 3 4 5 6 7 8

    Node* found = tree.search(4);
if (found)
        cout <<"Found: " << found->key << endl;

    cout <<"Min: " << tree.minimum(tree.getRoot())->key << endl;// 2
    cout <<"Max: " << tree.maximum(tree.getRoot())->key << endl;// 8

return0;
}

10. Red-Black Trees


1. 개요

1.1 정의

  • 레드-블랙 트리는 이진 탐색 트리 + 각 노드에 1비트 추가 (색상: RED 또는 BLACK)
  • 모든 리프는 비어있고(nil) 검은색
  • 단일 센티널 nil[T]를 모든 리프에 사용
  • color[nil[T]] = BLACK
  • 루트의 부모도 nil[T]

1.2 레드-블랙 속성 (5가지)

  1. 모든 노드는 RED 또는 BLACK
  2. 루트는 BLACK
  3. 모든 **리프(nil[T])**는 BLACK
  4. RED 노드의 자식은 모두 BLACK (연속된 RED 노드 불가)
  5. 각 노드에서 리프까지의 모든 경로는 같은 수의 BLACK 노드를 포함

1.3 예시

        (7,B)
       /     \
    (3,R)    (18,R)
    /  \      /    \
 (nil) (nil) (10,B) (22,B)
              /  \    /  \
           (8,R)(11,R)(nil)(26,R)


2. 높이 분석

2.1 정의

  • 높이(height): 리프까지의 가장 긴 경로의 간선 수
  • 흑색 높이(black-height):

    bh(x) = x에서 리프까지의 경로에 있는 BLACK 노드 수 (x 제외, nil[T] 포함)

속성 5에 의해 잘 정의됨


2.2 높이 상한 정리

보조정리 1

높이가 h인 노드의 흑색 높이 ≥ h / 2

보조정리 2

노드 x를 루트로 하는 서브트리는 ≥ 2^bh(x) - 1 개의 내부 노드를 포함

정리

n개의 내부 노드를 가진 레드-블랙 트리의 높이 ≤ 2 lg(n + 1)

증명

  • h = 높이, b = 루트의 흑색 높이
  • n ≥ 2^b - 1 ≥ 2^(h/2) - 1
  • h ≤ 2 lg(n + 1)

3. 연산의 시간복잡도

3.1 비수정 연산

연산시간복잡도
SEARCHO(lg n)
MINIMUMO(lg n)
MAXIMUMO(lg n)
SUCCESSORO(lg n)
PREDECESSORO(lg n)

모두 O(height) = O(lg n)


3.2 수정 연산

연산시간복잡도최대 회전 수
INSERTO(lg n)2회
DELETEO(lg n)3회

4. 회전 (Rotations)

4.1 개요

  • 기본적인 트리 재구조화 연산
  • 레드-블랙 트리를 균형 이진 탐색 트리로 유지하는 데 필요
  • 포인터 구조만 변경 (노드 값은 그대로)
  • 이진 탐색 트리 속성 유지
  • 좌회전과 우회전은 서로 역연산

4.2 회전 구조

LEFT-ROTATE(T, x)

xy
       / \                         / \
      αy         →x   γ
         / \                     / \
        β   γ                   α   β

RIGHT-ROTATE(T, y)

yx
         / \                       / \
x   γ       →             αy
       / \                           / \
      α   β                         β   γ


4.3 의사코드 (Left Rotate)

LEFT-ROTATE(T, x)
    y=right[x]
right[x]=left[y]
    ifleft[y] ≠ nil[T]
        p[left[y]]= x
    p[y]= p[x]
    if p[x]= nil[T]
        root[T]= y
else if x=left[p[x]]
left[p[x]]= y
else
right[p[x]]= y
left[y]= x
    p[x]= y


4.4 시간복잡도

  • O(1) — 상수 개의 포인터만 수정

5. 삽입 (Insertion)

5.1 기본 절차

  • 일반 BST 삽입 수행
  • 새 노드 z를 RED로 색칠
  • RB-INSERT-FIXUP 호출하여 속성 복구

5.2 의사코드

RB-INSERT(T, z)
    y= nil[T]
    x= root[T]
while x ≠ nil[T]
        y= x
if key[z]< key[x]
            x= left[x]
else
            x= right[x]
    p[z]= y
if y= nil[T]
        root[T]= z
elseif key[z]< key[y]
        left[y]= z
else
        right[y]= z
    left[z]= nil[T]
    right[z]= nil[T]
    color[z]= RED
    RB-INSERT-FIXUP(T, z)


5.3 위반 가능한 속성

속성위반 여부
1OK
2z가 루트면 위반
3OK
4p[z]가 RED면 위반
5OK

5.4 FIXUP 의사코드

RB-INSERT-FIXUP(T, z)
    while color[p[z]]= RED
        if p[z]=left[p[p[z]]]
            y=right[p[p[z]]]
            if color[y]= RED
                color[p[z]]= BLACK
                color[y]= BLACK
                color[p[p[z]]]= RED
                z= p[p[z]]
else
                if z=right[p[z]]
                    z= p[z]
LEFT-ROTATE(T, z)
                color[p[z]]= BLACK
                color[p[p[z]]]= RED
RIGHT-ROTATE(T, p[p[z]])
else
            (samewithleft/right exchanged)
    color[root[T]]= BLACK


5.5 Case 분석

Case 1: 삼촌 RED (재색칠)

  • P, y → BLACK
  • G → RED
  • z를 G로 이동

Case 2: 삼촌 BLACK, z가 오른쪽 자식

  • P 기준 좌회전
  • Case 3으로 이동

Case 3: 삼촌 BLACK, z가 왼쪽 자식

  • P → BLACK, G → RED
  • G 기준 우회전
  • 종료

5.6 시간복잡도 분석

  • RB-INSERT: O(lg n)
  • FIXUP:
    • Case 1만 반복 가능 (z가 2레벨 상승)
    • O(lg n) 반복
    • 최대 2회 회전

총 시간복잡도: O(lg n)


5.7 정확성 (루프 불변식)

while 시작 시:

  • z는 RED
  • 최대 하나의 위반만 존재
    • 속성 2: z가 RED 루트
    • 속성 4: z와 p[z]가 모두 RED

6. 삭제 (Deletion)

6.1 기본 절차

  • 일반 BST 삭제 수행
  • 실제 제거된 노드 y가 BLACK이면 FIXUP 호출

6.2 의사코드

(원문 그대로 유지)


6.3 y가 BLACK일 때 문제

  • 루트 RED 가능
  • 연속 RED 가능
  • BLACK 높이 1 감소

6.4 해결 아이디어

  • x를 “이중 흑색(doubly black)”으로 취급
  • 추가 BLACK을 위로 이동시켜 제거

6.5 FIXUP 의사코드

(원문 그대로 유지)


6.6 Case 분석

  • Case 1: 형제 RED
  • Case 2: 형제 BLACK + 두 자식 BLACK
  • Case 3: 형제 BLACK + 안쪽 RED
  • Case 4: 형제 BLACK + 바깥쪽 RED

6.7 시간복잡도

  • RB-DELETE: O(lg n)
  • FIXUP: O(lg n), 최대 3회 회전

7. 시간복잡도 정리

연산시간복잡도회전 횟수
SearchO(lg n)0
InsertO(lg n)≤ 2
DeleteO(lg n)≤ 3
Minimum / MaximumO(lg n)0
Successor / PredecessorO(lg n)0

8. C++ 구현 예시

8.1 노드 구조

c++
enumColor { RED, BLACK };

structNode {
int key;
    Color color;
    Node *left, *right, *parent;

Node(int k) :key(k),color(RED),left(nullptr),right(nullptr),parent(nullptr) {}
};


8.2 회전

(원문 코드 그대로)


8.3 삽입

(원문 코드 그대로)


9. 다른 균형 트리와 비교

트리삽입삭제검색특징
레드-블랙 트리O(lg n)O(lg n)O(lg n)실용적, 회전 적음
AVL 트리O(lg n)O(lg n)O(lg n)더 엄격한 균형, 검색 빠름
B-트리O(lg n)O(lg n)O(lg n)디스크 기반, 다진 트리

레드-블랙 트리 장점

  • 삽입/삭제 시 회전 횟수 적음 (최대 2~3회)
  • 실제 구현에서 좋은 성능
  • C++ STL map, set에서 사용

11. Elementary Graph Algorithms


1. 그래프 표현 (Graph Representation)

그래프 G=(V,E) 를 표현하는 대표적인 두 가지 방법


1.1 인접 리스트 (Adjacency Lists)

  • |V|개의 리스트 배열
  • 각 정점마다 하나의 리스트 보유
  • 정점 u의 리스트: (u, v) ∈ E 인 모든 정점 v 저장

무방향 그래프 예시

1--- 2
     |     
3--- 4
Adj[1] = [2, 3]
Adj[2] = [1, 4]
Adj[3] = [1, 4]
Adj[4] = [2, 3]

방향 그래프 예시

    1 → 2
    ↓   ↓
    3 → 4
Adj[1] = [2, 3]
Adj[2] = [4]
Adj[3] = [4]
Adj[4] = []

복잡도

연산시간복잡도
공간Θ(V + E)
u에 인접한 정점 나열Θ(degree(u))
(u, v) ∈ E 확인O(degree(u))

1.2 인접 행렬 (Adjacency Matrix)

  • |V| × |V| 크기의 행렬 A = (aᵢⱼ)

정의

  • aᵢⱼ = 1 if (i, j) ∈ E
  • aᵢⱼ = 0 otherwise

복잡도

연산시간복잡도
공간Θ(V²)
u에 인접한 정점 나열Θ(V)
(u, v) ∈ E 확인Θ(1)
  • 가중치 그래프: 비트 대신 가중치 값 저장

2. 너비 우선 탐색 (Breadth-First Search, BFS)


2.1 개요

입력

  • 그래프 G=(V,E)
  • 시작 정점 s ∈ V

출력

  • d[v] : s에서 v까지 최단 거리 (간선 수)
  • π[v] : v의 선행자

2.2 핵심 아이디어

  • s에서 파동(wave) 형태로 확산
    • 거리 1 → 거리 2 → 거리 3 …
  • FIFO 큐 사용 → wavefront 유지

2.3 정점 색상

색상의미
WHITE미발견
GRAY발견됨, 큐에 있음
BLACK탐색 완료

2.4 의사코드

BFS(G, s)
for each vertex u ∈ V[G] - {s}
        color[u] = WHITE
        d[u] = ∞
        π[u] = NIL

    color[s] = GRAY
    d[s] =0
    π[s] =NIL
Q= ∅
    ENQUEUE(Q, s)

while Q ≠ ∅
        u = DEQUEUE(Q)
for each v ∈ Adj[u]
if color[v] = WHITE
                color[v] = GRAY
                d[v] = d[u] +1
                π[v] = u
ENQUEUE(Q, v)
        color[u] = BLACK


2.5 BFS 속성

  • 큐에는 항상 d = i 또는 d = i+1 정점만 존재
  • d 값이 작은 정점이 앞쪽
  • 각 정점은 최대 한 번만 거리 값 부여
  • 연결되지 않은 경우, 일부 정점 도달 불가

2.6 시간복잡도

  • 전체: O(V + E)

구성

  • O(V) : 각 정점 최대 1회 enqueue
  • O(E) : 각 간선 검사
    • 방향 그래프: 최대 1회
    • 무방향 그래프: 최대 2회

3. 깊이 우선 탐색 (Depth-First Search, DFS)


3.1 개요

입력

  • 그래프 G=(V,E)

출력

  • d[v] : 발견 시간
  • f[v] : 완료 시간

특징

  • 가능한 깊게 탐색
  • 막히면 되돌아가 다른 분기 탐색
  • 시작 정점이 따로 없음 (전체 순회)

3.2 정점 색상

색상의미
WHITE미발견
GRAY발견됨, 탐색 중
BLACK완료

3.3 타임스탬프

  • 1부터 2|V| 까지 고유
  • 모든 v에 대해
1 ≤ d[v] < f[v] ≤ 2|V|

3.4 의사코드

DFS(G)
foreach vertex u ∈ V[G]
        color[u] = WHITE
        π[u] = NIL
time =0
foreach vertex u ∈ V[G]
if color[u] = WHITE
            DFS-VISIT(u)
DFS-VISIT(u)
    color[u] = GRAY
time =time +1
    d[u] =time
foreach v ∈ Adj[u]
if color[v] = WHITE
            π[v] = u
            DFS-VISIT(v)
    color[u] = BLACK
time =time +1
    f[u] =time

3.5 시간복잡도

  • Θ(V + E)
  • 모든 정점과 간선을 반드시 검사

3.6 괄호 정리 (Parenthesis Theorem)

모든 u, v에 대해 정확히 하나만 성립

서로 무관

d[u] < f[u] < d[v] < f[v]
또는
d[v] < f[v] < d[u] < f[u]

v가 u의 자손

d[u] < d[v] < f[v] < f[u]

u가 v의 자손

d[v] < d[u] < f[u] < f[v]

따름정리

  • v가 u의 진정한 자손 ⟺

    d[u] < d[v] < f[v] < f[u]


3.7 백색 경로 정리 (White-Path Theorem)

  • v가 u의 자손 ⟺
  • 시간 d[u] 시점에서
  • u → v로 가는 WHITE 정점만의 경로 존재

3.8 간선 분류

간선 유형설명
트리 간선DFS 숲에 포함
역방향 간선u가 v의 자손
순방향 간선v가 u의 자손 (트리 간선 아님)
교차 간선그 외

정리

  • 무방향 그래프 DFS → 트리 간선 + 역방향 간선만 존재

색상 기반 판별

v 색상간선 유형
WHITE트리 간선
GRAY역방향 간선
BLACK순방향 또는 교차

4. 위상 정렬 (Topological Sort)


4.1 DAG (방향 비순환 그래프)

  • 사이클 없는 방향 그래프
  • 부분 순서 모델링

4.2 위상 정렬 정의

  • (u, v) ∈ E 이면
  • u가 v보다 앞에 오는 순서

4.3 DFS 기반 위상 정렬

1. DFS 수행 → 완료 시간 f[v] 계산
2. f[v] 내림차순으로 정점 출력

구현 팁

  • 완료 시 리스트 앞에 삽입 → 정렬 불필요

4.4 DAG 판별 (사이클 검출)

정리

  • 방향 그래프가 DAG ⟺ DFS에서 역방향 간선 없음

5. 칸 알고리즘 (Kahn's Algorithm)


5.1 개요

  • BFS 유사 방식 위상 정렬
  • 핵심: 진입 차수 0 정점 반복 제거

5.2 동작 원리

  1. indegree = 0 인 정점 집합 S
  2. S에서 하나 제거 → 결과 L에 추가
  3. 이웃 indegree 감소
  4. 0 되면 S에 추가
  5. 반복

5.3 사이클 검출

  • 처리 후 간선 남아 있으면 → 사이클 존재

5.4 의사코드

KAHN_TOPOLOGICAL_SORT(G):
    L = []
    S = { v | indegree[v] =0 }

while Snot empty
        n = removefrom S
        append nto L
foreach m neighborof n
            remove (n, m)
            indegree[m]--
if indegree[m] ==0
add mto S

if edges remain
        error
else
return L


5.5 복잡도

항목복잡도
시간Θ(V + E)
공간O(V)

5.6 S 자료구조 선택

자료구조결과 순서
QueueBFS 유사
StackDFS 유사
Set / List임의
Priority Queue사전순 최소

5.7 DFS vs 칸 알고리즘

측면칸 알고리즘DFS 기반
방식BFS 유사재귀
사이클 검출직접간접
자료구조Queue / SetStack

6. 강한 연결 요소 (SCC)


6.1 정의

  • 최대 정점 집합 C
  • 모든 u, v ∈ C 에 대해
    • u → v 경로 존재
    • v → u 경로 존재

6.2 전치 그래프

  • 모든 간선 방향 반전
  • Θ(V + E)
  • SCC 동일

6.3 요소 그래프

  • 각 SCC를 하나의 정점으로 압축
  • 결과 그래프는 DAG

6.4 코사라주 알고리즘

  1. DFS → 완료 시간 계산
  2. Gᵀ 생성
  3. 완료 시간 내림차순으로 DFS
  4. 각 DFS 트리 = 하나의 SCC

6.5 핵심 보조정리

  • C → C' 간선 존재 ⇒
  • f(C) > f(C')

6.6 정확성 핵심

  • 가장 큰 f(C)에서 시작
  • 다른 SCC로 못 넘어감
  • DFS 트리 하나 = 정확히 한 SCC

7. C++ 구현 예시

7.1 BFS

c++
#include<iostream>
#include<vector>
#include<queue>
usingnamespace std;

vector<int>bfs(int start, vector<vector<int>>& adj,int n) {
vector<int>dist(n + 1,-1);
    queue<int> q;

    dist[start] =0;
    q.push(start);

while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : adj[u]) {
if (dist[v] ==-1) {
                dist[v] = dist[u] +1;
                q.push(v);
            }
        }
    }
return dist;
}


7.2 DFS

c++
int timeStamp =0;

voiddfsVisit(int u, vector<vector<int>>& adj,
              vector<int>& color, vector<int>& d, vector<int>& f) {
    color[u] =1;
    d[u] = ++timeStamp;

for (int v : adj[u])
if (color[v] ==0)
dfsVisit(v, adj, color, d, f);

    color[u] =2;
    f[u] = ++timeStamp;
}


7.3 위상 정렬 (DFS)

c++
voidtopoSort(int u, vector<vector<int>>& adj,
              vector<bool>& visited, stack<int>& st) {
    visited[u] =true;
for (int v : adj[u])
if (!visited[v])
topoSort(v, adj, visited, st);
    st.push(u);
}


7.4 칸 알고리즘

c++
vector<int>kahnTopologicalSort(vector<vector<int>>& adj,int n) {
vector<int>indegree(n + 1,0);
for (int u =1; u <= n; u++)
for (int v : adj[u])
            indegree[v]++;

    queue<int> q;
for (int u =1; u <= n; u++)
if (indegree[u] ==0)
            q.push(u);

    vector<int> result;
while (!q.empty()) {
int u = q.front(); q.pop();
        result.push_back(u);
for (int v : adj[u])
if (--indegree[v] ==0)
                q.push(v);
    }

if (result.size() != n)return {};
return result;
}


7.5 SCC (코사라주)

c++
vector<vector<int>>findSCC(vector<vector<int>>& adj,int n) {
vector<bool>visited(n + 1,false);
    stack<int> st;

    function<void(int)> dfs1 = [&](int u) {
        visited[u] =true;
for (int v : adj[u])
if (!visited[v])dfs1(v);
        st.push(u);
    };

for (int u =1; u <= n; u++)
if (!visited[u])dfs1(u);

    vector<vector<int>>radj(n +1);
for (int u =1; u <= n; u++)
for (int v : adj[u])
            radj[v].push_back(u);

fill(visited.begin(), visited.end(),false);
    vector<vector<int>> sccs;

    function<void(int, vector<int>&)> dfs2 = [&](int u, vector<int>& comp) {
        visited[u] =true;
        comp.push_back(u);
for (int v : radj[u])
if (!visited[v])dfs2(v, comp);
    };

while (!st.empty()) {
int u = st.top(); st.pop();
if (!visited[u]) {
            vector<int> comp;
dfs2(u, comp);
            sccs.push_back(comp);
        }
    }

return sccs;
}


8. 시간복잡도 요약

알고리즘시간복잡도비고
BFSO(V + E)최단 경로 (무가중치)
DFSΘ(V + E)전체 탐색
위상 정렬 (DFS)Θ(V + E)DAG
칸 알고리즘Θ(V + E)사이클 검출
SCC (코사라주)Θ(V + E)DFS 2회

25 기말고사

BFS psudocode 작성하기

djikstra 구현: 과정 별 최소 거리 작성하기

레드블랙트리 회전 어디로 돌았는지

Seoul, South Korea

jwsong5160@gmail.com

© 2026 Junwoo Song