이번 글에서는 알고리즘의 계산복잡도 함수가 재귀식(Recurrence relation) 내지 점화식 형태로 표현되는 경우를 살펴보도록 하겠습니다. 재귀식 또는 점화식이란 피보나치 수열(다음 피보나치 수는 바로 앞의 두 피보나치 수의 합이 되는 수열)처럼 수열의 항 사이에서 성립하는 관계식을 말합니다. 이로부터 데이터 수 에 대해 닫힌 형태(closed-form expression)의 정확한 계산복잡도 함수를 찾는 것이 이 글의 목표입니다. (복잡도의 기준은 알고리즘이 필요로 하는 시간과 메모리 등 자원인데 전자를 ‘시간복잡도’, 후자를 ‘공간복잡도’라 합니다) 이번 글 역시 고려대 김선욱 교수님 강의를 정리하였음을 먼저 밝힙니다. 그럼 시작하겠습니다.
재귀식 형태로 표현된 시간복잡성
알고리즘의 계산복잡도 함수가 재귀식으로 표현되는 대표적인 사례 가운데 하나가 분할-정복(divide-and-conquer) 문제입니다. 다음과 같이 풉니다. 우선 원래 문제를 부분문제로 쪼갭니다(divide). 부분 문제를 풉니다(conquer). 이를 합칩니다(merge). 원래 문제에 개의 데이터가 있고, 이를 푸는 데 드는 시간복잡도를 이라고 할 때 은 다음과 같이 분해할 수 있습니다.
이를 합병정렬(merge sort)을 예시로 설명하겠습니다. 합병정렬 관련 자세한 내용은 이곳을 참고하시면 좋을 것 같습니다.
는 문제를 분할하는 데 걸리는 시간을 가리킵니다. 합병정렬을 예로 들면 원래 문제를 두 개로 나누기 위해 데이터를 반으로 자를 위치(인덱스) 하나만 고르면 되므로 입니다. (Big notation 관련해서는 이곳을 참고하시면 좋을 것 같습니다)
는 (분할한 문제의 수 * 하위문제를 푸는 데 걸리는 시간)을 의미합니다. 원래 문제를 절반씩 두 개로 나누었고, 각 하위문제는 개의 데이터가 있으므로 가 될 것입니다.
는 하위문제를 합치는 데 걸리는 시간입니다. 합병정렬에서는 하위문제를 합치면서 정렬을 수행합니다. 일 때 다음 그림과 같습니다.
위 그림을 자세히 보면 하위 첫번째 array의 첫번째 요소(2)와 두번째 array의 첫번째 요소(1)을 비교해 sorted array의 첫번째 요소를 결정(1)합니다. 그 다음으로 첫번째 array의 첫번째 요소(2)와 두번째 array의 두번째 요소(2)를 비교해 sorted array의 두번째 요소를 결정(2)합니다. 이런 방식으로 merge를 모두 수행하는 데 총 회의 연산을 수행하게 됩니다. 따라서 데이터 수가 일 때 이 됩니다.
이를 바탕으로 합병정렬의 시간복잡도를 다시 쓰면 다음과 같습니다.
하지만 위와 같은 재귀식 형태로는 해당 알고리즘의 시간복잡도를 정확히 알기가 어렵습니다. 을 어렵게 구했지만 내부에 가 또 있어서 꼬리에 꼬리를 물고 이 1이 될 때까지 식을 길게 늘여뜨려 써야하기 때문입니다. 이를 닫힌 형태로 구해보기 위해 다음 세 가지 방법을 씁니다.
Substitution method
이 방법은 (1) 해당 알고리즘의 시간복잡도를 에 대한 함수로 가정한 뒤 (2) 이를 귀납(induction)에 의해 증명하는 방식입니다. 합병정렬을 예로 들면, 시간복잡도 함수 의 이 일 거라 우선 가정해보는 것입니다. (알고리즘 계산복잡도를 따질 때 상수항은 무시하므로 은 없는 것으로 취급)
이를 바탕으로 (2) 귀납에 의한 증명을 해보면 다음과 같습니다.
(2-1) 일 때 성립함을 보임
(2-2) 보다 작은 임의의 에 대해 성립한다고 가정
(2-3) 일 때도 성립함을 보임
따라서 입니다. 하지만 Substitution method의 첫 단추인 가정 부분을 제대로 설계하지 않으면 증명이 불가능하다는 단점이 있습니다.
Iteration method
이 방법은 점화식을 무한히 풀어 헤쳐 모두 더하는 방식으로 구합니다. 을 구해보겠습니다.
위 식에서 가 1이 될 때까지 끝까지 반복해서 더합니다. 이 때 는 입니다. () 그런데 여기에서 로그의 성질에 의해 이 되므로 다음이 성립합니다.
수식 말고도 트리 구조를 활용해 직관적으로 구하는 방법도 있습니다. 을 구해보겠습니다. 다음 그림과 같습니다.
우선 알고리즘이 한 개의 데이터를 처리하는 데 필요한 시간을 라고 두면, 개 데이터를 처리하는 데 드는 시간복잡도는 입니다. 위 점화식은 한번 분기할 때마다 데이터를 각각 1/3, 2/3씩 나누게 됩니다.
따라서 위 그림 트리를 번 분기했을 때 말단 좌측 끝 노드의 처리 대상 데이터 수는 가 됩니다. 이 말단 노드의 데이터 수가 1이 될 때까지 분기를 했을 경우 그 분기 횟수 는 이 됩니다. ()
마찬가지로 트리를 번 분기했을 때 말단 우측 끝 노드의 데이터 수는 가 됩니다. 이 말단 노드의 데이터 수가 1이 될 때까지 분기를 했을 경우 그 분기 횟수 는 이 됩니다. ()
점근 표기법(자세한 내용은 이곳 참고)은 데이터 수 에 대해 최고차항의 차수만 따집니다. 그런데 위 트리에서 각 층을 연산하는 데 , 분기 횟수는 최대 이 되므로 이 알고리즘의 계산복잡도는 가 됩니다.
이렇게 놓고 보면 재귀적으로 분기하는 함수에서 분기 횟수는 분기 비율의 역수를 밑으로 하고 데이터 수를 진수로 하는 로그값임을 알 수 있습니다. 하지만 Iteration method은 점화식이 수렴하지 않고 발산하는 형태일 경우 적용할 수 없습니다.
Master method
이 기법은 알고리즘의 계산복잡도 함수가 형태일 경우 단번에 닫힌 형태를 구할 수 있는 마법과 같은 방법입니다. (단 ) 여기에서 와 의 크기를 비교해 다음과 같이 한번에 닫힌 형태를 도출합니다. 그러나 아래 세 가지 경우를 제외한 함수에 대해서는 Master method를 적용할 수 없습니다.
case 1
→
case 2
→
- 단 로그를 제외한 최고차항의 차수가 같고 이어야 함
case 3
→
- 단 충분히 큰 과 1보다 작은 에 대해 을 만족해야 함
예를 들어보겠습니다.
- 이고 이므로
case 1
에 해당 - 따라서
- 이고 이므로
- 이고 이므로
case 2
에 해당하는지 체크 : , 비교 - 로그를 제외한 최고차항의 차수가 3으로 같고 일 때
case 2
가 성립 - 따라서
- 이고 이므로
- 이므로
case 3
에 해당 - 를 만족하는 1보다 작은 가 존재
- 따라서
- 이므로
Recursive Matrix Multiplication에 적용
지금까지 말씀드린 기법을 Recursive Matrix Multiplication에 적용해보겠습니다. 이 기법은 정방행렬을 행렬 4개로 나눈 뒤 내적(inner product)해 공간복잡도를 줄이는 방법입니다. 이같은 방식을 타일링(tiling)이라고 합니다. 와 를 내적해 를 얻는 연산()의 경우 다음과 같이 분할-정복 방식으로 나눠풀 수 있습니다.
정방행렬을 내적하는 데 필요한 계산복잡도를 이라 할 때 이를 다음과 같이 분해할 수 있습니다.
입니다. 1부터 의 범위에서 행렬을 어디서 나눌지 한번만 결정하면 되기 때문입니다.
입니다. 위의 수식처럼 행렬 내적을 8번 수행해야 합니다.
입니다. 예컨대 과 을 더해 을 만든다고 할 때 정방행렬의 전체 요소 개의 1/4만큼 덧셈 연산을 수행해야할 것입니다. 이를 4회 반복해야 하므로 이같은 결과가 나옵니다.
이를 바탕으로 을 다시 쓰면 다음과 같습니다.
위 점화식 형태의 함수를 Master method를 이용해 닫힌 형태로 나타내면 다음과 같습니다.
- 이므로
case 1
에 해당 - 따라서
'Algorithm > Theory' 카테고리의 다른 글
그래프 탐색 알고리즘(Graph Search Algorithm) (0) | 2022.01.29 |
---|---|
세그먼트 트리(Segment tree) - 구간 합 구하기 (0) | 2018.04.25 |
알고리즘 분석 (0) | 2018.04.14 |
정렬 알고리즘 - Quick Sort (0) | 2018.04.13 |
quick sort(1) (0) | 2018.04.13 |