티스토리 뷰

728x90

MIT의 알고리즘 수업 강의 자료를 참고하였습니다.

 

왜 정렬하는가?

  • 확실한 응용 사례
    • MP3 보관함 정렬
    • 전화번호부 정리
  • 검색하기 쉬움
  • 정렬하면 쉬워지는 문제들
    • 중간값 또는 가장 가까운 쌍 찾기
    • 이진 탐색, 통계적 이상치 확인
  • 응용 사례
    • 데이터 압축
    • 컴퓨터 그래픽(화면 렌더링)

 

Finding a median

 

중간값을 찾아보겠습니다. 정렬되지 않은 배열 A를 정렬된 B로 바꾸려고 합니다.

 

array A[0:n] → B[0:n]

unsorted → B[n/2]

 

  • 입력값으로 비교 함수를 갖는 정렬 알고리즘이 있다면 충분한 시간이 흐른 뒤에 정렬된 배열 B를 얻을 수 있음
  • 정렬된 배열이 있다면 상수의 시간이 걸림

 

Binary Search

 

이진 탐색을 하는 경우입니다.

 

A[0:n] looking for specific item $K$

compared k → B[n/2]

 

  • k를 B[n/2]와 비교합니다.
  • B는 정렬되었고 배열의 절반만 보게 됩니다.

 

Insertion Sort

삽입 정렬입니다. 배열의 모든 요소를 배열의 시작부터 끝까지 비교해 가면서 적절한 위치에 삽입하는 정렬 알고리즘입니다.

 

For $i = 1, 2, …, n$

insert $A[i]$ into sorted array $A[0:i-1]$

by pairwise swaps down to the correct position

 

 

예시를 살펴보겠습니다. 아래 [5, 2, 4, 6, 1, 3]의 배열이 있다고 가정합니다.

 

5 2 4 6 1 3

2 → key

 

5와 2를 비교하고, 2가 더 작으니까 key로 지정합니다.

 

swap (1) - 2와 5를 바꿉니다.

 

2 5 4 6 1 3

4 → key

 

5와 4를 비교하고 4를 key로 지정합니다.

 

swap (2) - 4와 5를 바꿉니다.

 

해당 과정을 반복합니다.

 

2 4 5 6 1 3

6 → key

 

 

swap (3)

1 2 4 5 6 3

3 → key

 

 

swap (4)

1 2 3 4 5 6

 

 

Q. How many steps do I have?

A. $\theta(n)$ steps (key positions)

 

몇 번의 스텝이 있었습니까? $\theta(n)$ 의 steps이 존재하게 됩니다.

 

그 의미는 키의 이동도 포함해서 각 단계에서 $\theta(n)$ 만큼 수행하고, 여기서 swap은 $\theta(n)$ 번 compare과 swap을 한다는 걸 의미합니다.

 

  • each step is $\theta(n)$ swaps.
  • 각 단계가 $\theta(n)$ 이기 때문에 $\theta(n^2)$ algorithms이다.
  • $\theta(n^2)$ compare & $\theta(n^2)$ swaps

 

Compare >> swaps

  • 실제론 비교가 대체적으로 더 높은 비용이 듦
  • 하지만 같다고 가정하고 살펴볼 것
  • $\theta(n^2)$ comparrison set

 

compare with the middle → Binary search

 

I’m going replace this(pairwise swaps) with binary search. Do a binary serach on $A[0:i-1]$ already sorted in $\theta(n log n)$ time.

 

  • pariwise swaps을 Binary Search로 바꾼다.
  • 이미 정렬되어 있기 때문에 Binary search는 $\theta(log n)$ 시간이 걸림
  • 하지만 삽입 후 항목을 옴기는 것도 $\theta(n)$ 시간이 걸리므로
  • $\theta(n log n)$만 비교하면 된다.
  • $\theta(n^2)$ swap → $\theta(n log n)$

 

merge sort로 넘어가기 전에 해당 개념을 살펴보겠습니다.

 

Divide and Conquer

분할 정복은 문제를 분할하고, 분할한 문제를 해결한 다음에 결과를 조합하는 알고리즘입니다. 큰 문제를 작은 문제로 분할한다는 관점에서 하향식 접근 방법(Top-down)으로 문제를 푼다고 볼 수 있습니다.

 

  • 분할: 문제를 동일한 유형의 여러 하위 문제로 나누는 것
  • 해결: 가장 작은 단위의 하위 문제를 해결하는 것
  • 조합: 하위 문제에 대한 결과를 원래 문제에 대한 결과로 조합하는 것

 

분할 정복 알고리즘은 보통 재귀를 사용하여 구현합니다.

 

아래의 코드는 분할 정복의 대표적인 문제인 피보나치 수열 코드입니다.

 

def fibb(n):
  if n <= 1:
    return 1
  return fibb(n-1) + fibb(n-2)

 

Merge sort

 

합병 정렬도 Divide&Conquer(분할 정복)을 사용합니다.

 

A[0:n]

L= A[0:n/2-1] R …

 

 

[ A ]

Split two parts L and R

  • Left와 Right로 나눕니다.

 

Sort

L’ R’

  • 정렬합니다.

 

Merge

  • 정렬된 L'과 R'을 합칩니다.

 

sotrted array A

  • input size: $n$
  • two arrays of size $n/2$
  • 2 sorted arrays of size $n/2$
  • sorted array of size $n$

→ 입력값의 크기는 n이고, 정렬되어 있는 두 개의 n/2의 크기의 배열을 갖습니다. 결국 n의 크기를 갖는 정렬된 배열을 갖게 됩니다.

 

merge algorithm이 작동하는 예시를 살펴보겠습니다.

 

  • Merge: Two sorted arrays an input
L' R'
20 12
13 11
7 9
2 1

 

→ 1 2 7 9 11 12 13 20

 

정렬된 값을 얻을 수 있습니다.

 

 

해당 과정은 기본적으로 차례차례 올라갑니다. 몇 개의 포인터가 있고, 두 개의 배열을 비교하며 올라갑니다. 이게 바로 merge routine입니다.

 

모든 과정 중 실제 작업은 merge routine에서만 일어납니다. 정렬의 나머지 과정은 단순한 재귀 호출이기 때문인데요. 배열을 쪼갤 필요가 있습니다.

 

그 방법은 간단합니다.

 

배열 A[0:n]이 있다고 하면, n이 홀수인지 짝수인지에 따라 L을 A[0:n/2-1]라고 정하고 R도 비슷하게 나눕니다.

 

이 부분에서 코드를 짤 때 고민을 좀 했었는데, 홀수라면 2로 나누었을 때 정수로 떨어지지 않으니까 정수로 만들어줄 필요가 있었습니다. 그때 round 함수가 떠오르긴 했지만, 반올림을 하는게 반으로 나누는데 적절한 것 같진 않았습니다.

 

보통 인덱스가 0부터 시작하니까 우리는 실제로 n까지의 배열을 갖는다면 그 총 개수는 n보다 1이 더 많다고 생각이 들어서 전 math module의 ceil(올림) method를 사용하였습니다.

 

다시 수업 내용으로 돌아가서,

 

Q: 크기가 n/2인 두 배열이 있다면 합병 정렬의 복잡도는 얼마일까요?

A: $\theta(n)$입니다.

 

하지만 해당 알고리즘의 전체 복잡도는 $\theta(n log n)$입니다. 이것을 증명하기 위해 merge sorting 전체를 살펴봅니다. 그러기 위해 recursion tree를 살펴보게 됩니다. 이는 그림으로 증명하는 방법입니다.

 

 

Complexity

 

 

$T(n) = c_1 + 2T(n/2) + c n$

$= \alpha T(n/2) + \theta(n) + \theta(1)$

 

  • c1(divide): 배열을 나누기 위해 필요한 상수 시간
  • 2T(n/2): 크기가 n/2인 두 가지 문제, 재귀 부분
  • cn: merge 부분으로 상수 곱하기 n인데 이것은 $\theta(n)$의 복잡도와 관련된 것입니다.

 

트리 구조로 복잡도를 계산해보면 식이 아래처럼 나열됩니다.

 

c n

cn/2 cn/2

cn/4 cn/4 cn/4 cn/4

c - constant

 

 

해당 과정을 거치면 log(n)+1과 n개의 단말 노드가 생깁니다.

 

이 트리를 더 살펴보면 각 단계에서 작업을 진행하는데, 각 레벨마다 cn만큼의 작업을 진행합니다.

 

cn으로 모두 같습니다. 같은 양의 작업을 하는 것입니다.

 

이제 이 모든 작업이 (1+log(n)) 만큼 임을 알 수 있고, $(1+log(n)) \times cn =$ $\theta(n log n)$ 임을 알 수 있습니다.

 

 

$T(n) = (1 + logn) \times cn = \theta(nlogn)$

$T(n) = c_1 + 2T(n/2) + c n$

$= 2 T(n/2) + \theta(n) + \theta(1)$

 

What is one advantage of insertion sort over merge sort?

  • You don’t have to mote elements outside.
  • In-place sorting is something that has to do with auxiliary space
  • 삽입 정렬은 요소들을 정렬하기 위해 추가적인 공간이 필요 없습니다. 제자리 정렬이기 때문인데요.
  • 합병 정렬은 $L^\prime, R^\prime$이 필요합니다. 초기 정렬인 L과 R과는 다릅니다.

 

Merge - sort $\theta(n)$ auxiliary space (추가 공간이 필요함)

In-place sort → $\theta(1)$ auxiliary space (일정한 보조 공간을 가집니다.)

 

  • In-place merge-sort: 제자리 합병 정렬이란 것이 있음(참고만)

 

  • Merge sort in Python: $2.2 n log n$ ms
  • Insertion sort in Python: $0.2 n^2$ ms
  • Insertion sort in C: $0.01n^2$ ms

 

Recurrence solving

 

 

해당 강의에서 중요한 내용은

 

  1. 병합 정렬 방법 작동 원리
  2. Time complexity: Recursion Tree
  3. Space complexity: auxiliary space
  4. Recursion 있을 때, $T(n)$을 구하는 여러 cases

 

입니다.

 

 

감사합니다.

 

 

 

[Reference]

[1] Lecture 3: Insertion Sort, Merge Sort

[2] ai-tech interview 8-algorithm

728x90
댓글