참고 2. Binary Search Tree


Sanjoy Dasgupta가 저술한 책인 Algorithms과 이기창님의 블로그를 참고하였다.


1-1. Binary Search(이진탐색)

Binary Search Tree(이하 BST)를 이해하기 위해서는 먼저 Binary Search에 대해 알아야한다.

Binary Search : 오름차순으로 정렬된 리스트에서 특정한 값의 위치를 찾는 탐색 알고리즘. 중앙값보다 크면 오른쪽, 작으면 왼쪽으로 재귀적으로 탐색한다.

cf) Binary Search와 K-ary Search

둘 다 정렬된 리스트에서 특정값의 위치를 탐색하는 알고리즘이다. 하지만 Binary Search의 경우 중앙값을 대상으로 탐색값이 이 값보다 크냐 작냐에 따라 왼쪽, 오른쪽으로 움직이면 되는 반면, K-ary Search는 $k$ 개의 그룹 중 특정값이 어느 그룹인지 정해야하기 위해 같은 레벨에서 또 다시 비교를 해야한다. 따라서 시간복잡도의 경우 Binary Search가 $O(\log_2n)$, k-ary Search가 $O(\log_kn)$으로, Binary Search가 더 효율적인 것을 알 수 있다.


1-2. Binary Search(이진탐색)의 수행 과정

Binary Search는 Quick Sort와 그 방식이 약간 유사하다. 퀵 소트에서 pivot을 선정해 pivot의 앞 뒤로 리스트를 나누었던 것처럼, Binary Search도 중앙값을 기준으로 리스트를 앞, 뒤로 나누며 원하는 값을 탐색한다. 위의 그림을 예로 하여, 리스트에서 7을 찾아보자

  1. 리스트의 중앙값을 찾아 탐색값과 비교
  2. if 중앙값 > 탐색값 : 중앙값보다 큰 값들을 탐색 대상에서 제외
  3. if 중앙값 < 탐색값 : 중앙값보다 작은 값들을 탐색 대상에서 제외
  4. 2, 3 중 한 과정을 거쳐 남은 탐색 대상을 대상으로 1~3 과정 반복

따라서 위 그림에서는 중앙값(14) > 탐색값(7)이므로, 중앙값(14)보다 큰 18, … , 71을 제외한 뒤 다시 1, … , 14를 대상으로 또 중앙값(6)과 탐색값(7)을 비교하며 Binary Search를 진행하는 것이다. 그렇게 되면 최종적으로 중앙값 = 탐색값이 되어 탐색하고자 하는 값을 찾을 수 있다.


1-3. Binary Search(이진탐색)의 시간복잡도

데이터의 총 개수가 $n$개일 때, 이 데이터들은 Binary Search를 거치며 $n/2, n/4 …$로 개수가 줄어들게 된다. 따라서 시행횟수를 $k$라고 한다면, 최종적으로 Binary Search를 마쳤을 때 남은 데이터의 개수는 $n(\frac 1 2)^k$이 된다. 그리고 이 값은 중앙값 = 탐색값이므로 1이라고 할 수 있다. 따라서 $n(\frac 1 2)^k \approx 1$이므로 양변에 $2^k$를 곱해주면 $n \approx 2^k$가 된다. 즉, $k \approx log_2 n$이고, $k$는 시행 횟수이므로 시간복잡도는 $O(\log_2n)$이다.


2-1. Binary Search Tree(BST. 이진탐색트리)

BST : Binary Search와 연결리스트를 결합한 자료구조. Binary Search를 기반으로 연결리스트를 이용하여 원하는 값을 탐색하는 알고리즘

Binary Search의 경우 시간복잡도가 $O(\log_2n)$으로 빠르지만 자료의 입력, 삭제가 불가능

연결리스트의 경우 자료의 입력, 삭제가 가능하지만 시간복잡도가 $O(n)$으로 느림

따라서 이 두 놈들의 장점을 서로 보완하며 탄생된 것이 바로 BST이다.


2-2. Binary Search Tree(이진탐색트리)의 수행과정

Binary Search의 경우 리스트의 중앙값을 찾아 이를 기준으로 앞, 뒤를 나누는 과정을 반복하며 원하는 값을 탐색했다. BST도 이와 유사하다.

  1. 루트노드의 값와 탐색값을 비교
  2. if 루트노드 > 탐색값 : 왼쪽 서브트리로 이동
  3. if 루트노드 < 탐색값 : 오른쪽 서브트리로 이동
  4. 2, 3 중 한 과정을 거쳐 남은 트리를 대상으로 1~3 과정 반복

따라서 위 그림의 경우 내가 12를 찾고 싶다고 하자. 루트노드의 값이 18이므로 18 < 12이다. 따라서 왼쪽 서브트리로 이동한다. 그 다음 루트노드인 7과 비교하자. 7 < 12이다. 따라서 오른쪽 서브트리로 이동한다. 그 다음은? 루트노드 = 탐색값이다. 탐색이 끝났다.


2-3. Binary Serach Tree(이진탐색트리)의 시간복잡도

BST는 Binary Search의 특성인 탐색과 연결리스트의 특성인 삽입, 삭제가 모두 가능하다. 따라서 시간복잡도도 위의 경우를 모두 고려해야한다. 먼저 트리의 높이를 $h$라고 하자.

따라서 Best, Average-Case의 시간복잡도는 $O(h)$인 것을 알 수 있다.

하지만 Worst-Case의 경우 이야기가 달라진다. 지금까지 예를 든 Tree는 모두 노드가 두개의 자식트리를 그나마 균형적으로 갖는 것들이었다. 하지만 한쪽으로만 자식노드가 가져진다면 어떻게 될까?

이것이 그 예이다. 트리가 균형을 이루지 않고 한쪽으로만 쏠리는 것을 알 수 있다. 심지어 이것은 그나마 양호한 것이고, 만약 노드의 값이 2, 5, 7, 9, 13, …과 같이 한쪽으로만 커진다면 $n$개의 노드가 한쪽으로만 늘어지는 트리가 되어 높이가 $n$이 되버린다. 따라서 이 경우 Sequential Search와 같이 시간복잡도가 $O(n)$이 된다.

cf) Sequential Search(순차탐색) : 리스트 내에서 데이터를 탐색값과 순차적으로 비교해가며 탐색값을 찾는 것

Binary Search와 연결리스트의 장점을 모두 갖다붙였음에도 불구하고 BST에는 위와 같은 단점이 있다. 따라서 이 Worst-Case를 해결하기 위해서는 트리가 한 쪽으로만 자라지 않도록 Balance를 맞춰주어야 한다. 이를 위해 나온 것이 AVL Tree와 B-Tree이다. 이들은 이어서 공부할 것이다.