Algorithm/알고리즘 이론

알고리즘 #9_ 최적 이진 검색 트리(BST: Binary Search Tree)

Tigercow.Door 2017. 12. 10. 15:54


안녕하세요. 이번 포스팅에서는 동적 프로그래밍(Dynamic Programming)에서의 최적 이진 검색 트리(Optimal Binary Search Tree)에 대해서 알아보겠습니다.


1. 최적 이진 검색 트리 (Optimal Binary Search Tree)


최적 이진 검색트리를 보다 쉽게 이해하기 위해서 상황을 가정하며 설명드리겠습니다.

특정 문서에 중복이 허용된 여러개의 영어단어가 존재하는데 이를 한글로 번역해서 저장하는 프로그램을 필요로 한다고 가정합니다. 그렇다면 프로그램은 이를 수행하기 위해서 문서에 있는 각각의 영어단어에 접근하고, 이에 해당하는 한글 단어를 찾아야 합니다. n개의 영어 단어가 존재한다고 했을 때, 어떻게 해야 이를 효율적으로 수행하는 프로그램을 작성할 수 있을까요?


바로 이럴 때 사용될 수 있는 것이 이진 검색 트리입니다.

위의 상황을 생각하면 이때 이진 검색트리에서는 하나의 노드에 영어 단어에 해당하는 key와 그에 해당하는 한글데이터가 존재할 것 입니다. 문서에 있는 하나의 영어 단어 각각에 대해서 트리를 한번 검색해야 하므로 우리는 트리의 높이가 비슷하게 균형잡힌 이진 트리 검색을 이용한다면 단어 하나당 O(lg n)의 검색 시간을 보장할 수 있습니다.

그러나 이것이 과연 최적의 이진 트리일까요?

가정된 상황을 다시 살펴보면 영어 단어는 '중복'될 수 있다고 하였습니다. 즉, 특정 단어에 대해서는 빈번히 검색이 진행될 것이고 다른 단어에서는 검색이 거의 사용되지 않을 것 입니다.

이러한 상황에서 우리는 최적 이진 검색 트리(Optimal Binary Search Tree)를 사용할 수 있습니다.


최적 이진 검색 트리에서는, n개의 서로 다른 키 K = <k1, k2, ... , kn>이 오름차순으로 정렬된 상태로 주어지며 이러한 키를 바탕으로 이진 검색 트리가 구성됩니다. 그리고 각 키 ki에 대해 검색이 일어날 확률 pi또한 함께 존재합니다. 

추가적으로, 우리는 어떤 단어 K에 대해서 탐색이 되지 않을 경우도 고려해야 합니다. 따라서 가상키라는 개념을 통해 이를 해결합니다. 가상키는 이진 검색 트리의 리프 노드로 배치될 것이며 특정 단어 K가 어떤 키 ki를 발견하지 못하면 가상키를 발견하게 될 것이고 이는 단어에 대한 해석을 찾지 못한 것으로 판단할 수 있습니다.

이러한 가상키는 n개의 노드를 가지는 이진 검색 트리의 리프노드들이므로 총 n+1개이며 d0는 k1보다 작은 모든 값을 나타내고 dn은 kn보다 큰 모든 값을 나타냅니다.


아래 사진을 보면 두개의 이진 검색 트리를 확인할 수 있습니다.



그림 아래에 존재하는 표는 각각의 항목이 일어날(검색될) 확률을 의미합니다.

Search Cost의 계산은 각 항목의 탐색시간(방문하는 노드수 = 깊이+1) * 확률의 총합이라고 하겠습니다. 

먼저 첫번째 (a)의 이진 검색 트리를 보면 비슷하게 균형 잡힌 것을 볼 수 있습니다. 이러한 이진검색트리의 Search Cost는 그림에서 나온것과 같이 2.80입니다.

그리고 두번째, (b)의 이진 검색트리의 Search Cost는 2.75로써 (a)보다 작은 것을 볼 수 있습니다.


이렇게, 최적 이진 검색 트리는 단순히 높이를 균형있게 하는 것이 아니라 탐색시간을 최소로 하여 최적의 탐색시간을 갖도록 하는 것입니다.

일반적으로 최적 이진 검색 트리는 다음과 같이 정의됩니다.



위에서 언급한 바와 같이, 모든 탐색에서 방문하는 노드들의 회수의 최소를 갖도록 하는 것입니다.



2. 최적 이진 검색 트리의 최적 부분 구조



최적 이진 검색 트리의 최적 부분 구조를 찾기 위하여 부분 구조를 살펴보겠습니다.

어떤 이진 검색 트리의 서브 트리를 고려하면, 그 부분 구조는 1<= i <= j <= n에 대해 연속하는 범위 ki, ... , kj에 해당하는 키를 포함해야 합니다. 그리고 키 ki, ... , kj를 포함하는 서브트리는 그 해당 가상키 di-1, ... , dj도 포함해야 합니다.


이제 최적 이진 검색트리의 최적 부분 구조에 대해서 알아보겠습니다.

최적 이진 검색 트리 T가 있을 때, 키 ki, ... , kj를 포함하는 서브 트리 T'을 가진다면 이 서브 트리 T'은 키 ki, ... , kj와 가상 키 di-1, ... , dj를 가지는 부분 문제에 대해서도 최적이어야 합니다.

이러한 주장을 증명하기 위해서 cut&paste 방법을 적용합니다. T'보다 기댓값이 작은 서브 트리 T''이 존재한다고 가정합니다. 이때 T'을 잘라내고 그 자리에 T''을 붙여 넣으면 T보다 기댓값이 작은 이진 검색 트리가 나타나게 되고 이것은 T가 최적이라는 가정에 모순입니다.



그럼 이제 이러한 최적 부분 구조를 사용해서, 부분 문제에 대한 최적해를 구함으로써 주어진 문제에 대한 최적해를 구할 수 있음을 보여야 합니다.



위의 그림처럼 루트가 kr이고 키 ki, ... , kj를 갖는 최적 서브 트리가 있다고 합시다.(i <= r <= j)

그렇다면 아래 그림과 같이, 루트 kr의 왼쪽 서브 트리키는 ki, ... kr-1과 가상키 di-1, ... , dr-1를 포함할 것이며 오른쪽 서브 트리는 키 kr+1, ... , kj와 가상키 dr, ... , dj를 포함합니다. 이때 루트 kr에 대해 i부터 j까지의 모든 후보 루트 ki, ... , kj를 고려하면서 왼쪽 서브 트리로 ki, ... , kr-1를 포함하며 오른쪽 서브 트리로 kr, ... , kj를 포함하는 모든 최적 이진 검색트리를 검색하면 최적의 이진 검색 트리를 발견할 수 있습니다.



이때 만약 서브 트리의 루트를 ki로 선택한다면 왼쪽 서브 트리는 ki, ... ki-1을 포함합니다. 즉, 왼쪽 서브 트리는 어떤 키도 갖지 않는 빈 서브 트리가 됩니다. 그러나 왼쪽 서브 트리가 아무것도 갖지 않는 것은 아닙니다. 바로 가상키를 갖기 때문이죠. 왼쪽 서브 트리는 di-1, ... dr-1 의 가상키를 갖는다고 했으니, 루트가 ki인 서브 트리의 왼쪽 서브 트리는 di-1이라는 하나의 가상키만을 갖게 됩니다. 이것은 kj를 서브 트리의 루트로 잡았을때 오른쪽 서브트리에서 갖은 상황이 발생합니다.



3. 재귀해


이제 재귀적으로 최적해의 값을 찾을 준비가 되었습니다.

i >= 1, j <= n, j >= i-1 인 키 ki, ... , kj를 포함하는 최적 이진 검색 트리를 찾는 것으로 부분 문제의 범위를 정합니다.

위에서 언급한 바와 같이 j=i-1 일때는 실제키가 없고 가상키 di-1만 가지게 됩니다.

그리고 키 ki, ... , kj를 포함하는 최적 이진 검색 트리를 찾는 기대비용을 e[i,j]로 정의하도록 하겠습니다.

결국 우리의 목표는 e[i,n]을 찾는 것입니다.


먼저, 우리는 j=i-1에 대해서 값을 쉽게 찾을 수 있습니다. 가상키 di-1만 존재하므로 기대 검색 비용, e[i,i-1] = qi-1 입니다.

이제 j >= i 일 때에 대해서 생각합니다. ki, ... , kj 중에서 루트 kr을 선택해야 하며 왼쪽 서브 트리가 최적 이진 검색트리가 되어야 하며 오른쪽 서브 트리 또한 최적 이진 검색트리가 되어야 합니다. 

이때 고려할 점이 있습니다. 만약 트리 T가 존재할 때 이 트리가 어떤 루트 N에 대해서 서브 트리가 된다면 T라는 트리의 검색비용에는 무슨 일이 벌어질까요? T 트리에 있는 모드의 깊이가 모드 1씩 증가하므로 T 트리의 검색 비용은 모든 노드들이 검색될 확률의 총합만큼 증가하게 됩니다.

그럼 점화식을 한번 알아보도록 하겠습니다. 



먼저 위의 그림에서,



이것은, ki, ... ,kj 의 키를 가진 서브 트리의 모든 확률의 합을 나타냅니다.

이를 통해 점화 식을 한번 세워보면, 위의 그림의 첫번째 식인,

e[i,j] = pr + (e[i,r-1] + w(i,r-1)) + (e[r+1,j] + w(r+1,j)) 이 나오게 됩니다.

식에서 의미하는 바를 그림으로 알아보겠습니다.



먼저 식에서 첫번째 인자인 pr은 위의 그림에 있는 트리의 루트가 검색될 확률을 의미합니다.

루트의 깊이는 0이므로 검색이 실행될때 pr*1만큼의 검색비용이 필요합니다.

그리고 e[i,r-1]은 왼쪽 서브트리, 그림에서 초록색을 가진 서브트리에 대한 검색비용을 의미합니다.

w(i,r-1)은 왼쪽 서브트리의 깊이가 1만큼 증가했으므로 그에 대해 왼쪽 서브트리의 모든 노드들의 확률의 합을 의미합니다. 그림에서는 빨간색 선으로 생각하시면 되겠습니다.

마찬가지로, e[r+1,j]는 그림에서 주황색테두리를 가진 서브트리를 의미하며, w(r+1,j)는 그림에서 파란색 선을 의미합니다.


위의 점화식을 보다 간단하게 하기 위해

w(i,j) = w(i,r-1) + pr + w(r+1,j)

라는 사실을 이용합니다.

그러면 위의 점화식은

e[i,j] = e[i,r-1] + e[r+1,j] + w(i,j)

가 됩니다.


그리고 r을 범위내 모든 값에 대해 탐색을 진행하고 그 중 최소의 검색 비용을 갖는 r을 찾아야 하므로 최종적인 점화식은 아래와 같습니다.




4. 알고리즘


먼저 Optimal BST를 구하기 위한 수도코드는 아래와 같습니다.



동적 프로그래밍을 이용하여 보다 효율적으로 탐색을 진행하기 위해 수도코드에서는 총 3개의 테이블을 사용합니다.

그리고 앞에서 이야기 했던 것처럼 e값을 재귀적으로 도출하면서 최종적인 목표에 도달하게 됩니다.


글의 서두에서 보여드렸던 키들을 이용하여 Optimal BST으로 계산하면 아래와 같은 테이블이 생성됩니다.


이러한 Optimal BST의 총 시간 복잡도는 O(n^3)입니다.


이렇게 하여 최적 이진 검색 트리(Optimal Binary Search Tree)에 대해서 알아보았습니다.

추가적인 궁금사항이나 내용에 대한 피드백은 댓글을 이용해주세요 :)



728x90