0. 주의
- 단순 BST 문제와 & Optimal BST 문제는
- 다루는 자료구조 자체는 BST로 동일하지만, 주목하는 특징이 서로 다르다.
(1) BST의 경우 Search가 궁극 목적이다. 즉 Binary Tree 자료구조를 통해 우리가 아는 Binary Search(이진 탐색)을 해내는 게 핵심이다. (ex. 숫자 맞추기 게임에서 Up/Down를 알 수 있다고 하면, 일반적으로 가능한 범위를 반으로 잘라서 정중앙의 숫자를 말하는 것이 가장 효율적 - 점차 정답의 범위를 반씩 좁혀나감) 따라서 각 노드에 저장된 숫자(키/데이터)의 양적 비교가 중요하다.
(2) Optimal BST의 경우
형태는 BST로 똑같다. 그러나 이 경우 노드에 저장된 숫자의 양적 비교가 중요하지 않다. 오히려, 각 노드에 저장된 데이터는 문자열과 같이 더 다양한 데이터가 들어갈 수 있고, 우리가 '양적 비교'의 대상으로 고려해야 할 수치는 데이터 자체의 값이 아니라 메타데이터, 즉 데이터의 '검색 빈도(확률)' 및 각 데이터의 검색을 위한 '함수 호출 횟수'이다. 이들을 계산하여 '평균 검색 시간'을 최소화하는 트리 구조로 업그레이드시키는 것이 핵심이다.
따라서 완성된 Optimal BST의 모습을 보면, 1번 이진트리와 같이 숫자가 정렬된 상태가 아니다. 오히려 노드 데이터 자체는 마구잡이로 unsorted되었을 가능성이 높다. 노드의 정렬 기준이, 메타데이터 연산 결과를 Minimize하는 것이어야 하기 때문이다.
1. BST (Binary Search Tree) 이진 탐색 트리 (이진 트리 중 하나)
BST: ordered set (순서 가능한 집합)에 속한 원소(key)로 이루어진 Binary Tree
* Binary Tree: 각 노드의 자식의 수가 최대 2개인 트리
- 이진 트리 (Binary Tree: 각 노드의 자식의 수가 최대 2개인 트리) 이다.
- 왼쪽 자식 노드의 값은 자신의 값보다 작아야 한다.
- 오른쪽 자식 노드의 값은 자신의 값보다 커야 한다.
- 서브트리도 BST이다.
- 값이 중복되는 노드는 없다.
BST는 그 나름대로 풀어야 할 문제들의 특징이 있다!
https://gsmesie692.tistory.com/m/115
2. Optimal Binary Search Tree 최적 이진 검색 트리
→ Dynamic Programming으로 최적화
Goal: 평균 검색 시간이 제일 낮은, 가장 효율적인 트리를 찾자
1) 전제: 각 검색어의 검색 빈도(확률)이 다를 수 있다
ex) Crystal = 0.1 / Daisy = 0.2 / Beatrice = 0.3 / John = 0.4
2) 평균 검색 시간: 각 검색어의 검색 빈도(확률) p와, 검색을 위한 함수 호출 횟수(시간 복잡도) c의 가중합
- '평균'이라는 말에 마지막에 개수로 나눠야 하지 않는가 하고 생각할 수 있지만, 이미 '확률'을 곱하여 더하므로 그 자체로 평균이 됨
- 각 검색어의 검색 빈도는 상수임 (고정된 Input)
- 따라서 검색어들을 어떤 형태의 BST로 구성하느냐에 따라 c가 달라져서 전체 평균 검색 시간이 달라지는 것!
ex)
왼쪽: (0.1*1) + (0.2*2) + (0.3*2) + (0.4*3) = 2.3
오른쪽: (0.1*4) + (0.2*3) + (0.3*2) + (0.4*1) = 2.0
Problem Solving - Algorithm
1. brute-force
: 모든 경우의 수는 2^(n-1) →exponential
2. Dynamic Programming
- Bottom up
- 시간 복잡도: n^3 (for문이 3겹)
- Optimal Substructure: 어떤 BST의 왼쪽, 오른쪽 서브트리가 각각 최적 BST여야, 해당 BST도 최적이다.
- 메모를 위한 Table(Tabular) 2개 & for문만이 필요하다!!
노드(Key)가 1~n개 있다고 하자: K_1 ~ K_n
1) Table A:
K_i부터 K_j까지 최적의 BST로 정렬되어있다고 한다면, optimal 평균 검색 시간을 A[i][j]에 메모
- 원소가 하나인 트리의 경우, 원소를 찾는데 비교를 딱 한 번 하므로 A[i][i] = p_i
어떤 Optimal BST가 있고 그것의 root가 Key(Node)k라고 하자.
그러면 그것의 left subtree가 최적이고, right subtree 또한 최적이다.
(최적이 아니라면 그걸 cut 하고 더 최적인 subtree로 paste하면, 현재보다 더 좋은 bst가 된다. 그러나 이는 현재가 optimal bst라는 가정에 모순된다)
왼쪽 서브 트리의 최적 값은 A[1][k-1]로 메모되어 있음
오른쪽 서브트리의 최적 값은 A[k+1][n]로 메모되어 있음
- 이때 Key k를 트리의 root로 추가해서 전체 트리를 만든다면, 각 서브 트리에서의 비교 횟수는 기존의 비교 횟수에 추가로 한 번만 더 비교를 하면 됨(각 subtree의 노드마다, 검색을 위한 함수 호출 횟수가 1씩 늘어나기 때문임)
- root에 대한 평균 검색 횟수는: 1(depth) x p_root = p_k
→ 둘을 합친 전체 트리의 최적 값은 (A[1][k-1] + p_1 + ... p_k-1) + p_k + (A[k+1][n] + p_k+1 + ... + p_n)
2) Recurrence Relation
수많은 경우의 수 중 최적의 값은 반드시 존재한다(최솟값)
→ 1부터 n까지가 아니라 i번째부터 j번째까지의 key로 일반화
이 식을 이용해서 2차원 table A를 채워나가서, A[1][n] 의 값을 얻으면 그것이 최적 BST의 평균 검색 시간(최적값)이 됨
3) Table R:
R[i][j]: K_i부터 K_j까지 최적의 BST로 정렬되어을 때, optimal BST의 root의 인덱스 메모 (A 배열에 최적값을 넣는 순간의 k값)
- R의 필요성: A만으로 최적값은 구할 수 있지만 최적 BST의 모양을 알 수 없음
- root 인덱스가 저장되어 있으면, root 이후의 노드 인덱스는 어떻게 아는가? Recursion에 의해 채워진다. 즉, 루트가 된 노드가 k번 노드라고 치면 그 왼쪽 서브트리의 루트노드 (즉 k번 노드의 left child)는 R[1][k-1], 오른쪽 서브트리의 루트노드는 R[k+1][n] 이 된다. 이런 식으로 R 배열의 값들을 이용해 재귀함수를 호출해주면서 노드를 만들어나가면 최적 BST가 만들어진다.
https://gsmesie692.tistory.com/m/116
⭐️⭐️
https://hi-guten-tag.tistory.com/73
'개발 > CS study' 카테고리의 다른 글
[DP] Longest Common Substring & Longest Common Subsequence (0) | 2023.10.22 |
---|---|
[DP] 유명한 Knapsack problem으로 DP 특징 이해하기 (0) | 2023.10.22 |
[알고리즘] 퀵 정렬 (Quick sort) (0) | 2023.09.25 |
[백준/c++] 최대 힙 (0) | 2023.09.22 |
[알고리즘] 힙, 힙정렬 (0) | 2023.09.20 |