RB 트리를 C 프로그래밍으로 구현한 과제 링크:
https://github.com/Vacayy/Red-Black-Tree
절대 완벽한 코드는 아니지만 만약 이 포스팅을 보는 분 중에 RB 트리 내부 구조가 궁금하신 분은 들어가보시면 참고가 될 듯!
C 언어를 처음 다뤄봤는데, 복잡한 자료구조를 직접 구현해보면서 C언어에도 익숙해질 수 있었던 과제였다
이진 탐색 트리(BST)는 각 노드가 최대 두 개의 자식을 가질 수 있는 이진 트리의 일종으로, 왼쪽 자식 노드의 값은 부모 노드의 값보다 작고, 오른쪽 자식 노드의 값은 부모 노드의 값보다 크다는 특성을 가진다. 이러한 특성 덕분에 BST는 중위 순회를 통해 정렬된 데이터를 얻을 수 있으며, 효율적인 검색, 삽입, 삭제 연산이 가능하다.
그러나 일반적인 이진 탐색 트리에서, 예를 들어 새롭게 삽입되는 모든 노드가 루트 노드보다 작은 값이라면 트리는 루트 노드를 기준으로 왼쪽으로만 계속 발달하게 될 것이다. 이전에 공부했다시피 이진 탐색 트리의 성능은 트리의 높이에 의존적이다. 탐색할 때 원하는 노드를 찾을 때 까지 연결을 따라 노드를 타고 가기 때문이다.
때문에, 트리가 한 쪽으로 치우쳐 불균형한 형태를 이루면 최악의 경우 연산의 효율성이 linked list와 같은 성능, 즉 O(n)으로 크게 저하될 수 있다. 이 문제를 해결하기 위해 균형을 자동으로 유지하는 여러 가지 이진 탐색 트리의 변형이 개발되었다.
균형 잡힌 트리는 데이터가 추가되거나 제거될 때, 트리의 높이가 최소화되도록 자동으로 조정한다. 그중 하나가 바로 RB 트리이다. 자기 균형 이진 검색 트리(Self-Balancing BST)이라고도 한다. RB 트리는 다양한 연산을 통해 높이를 로그 스케일로 제한한다.
(n개의 내부 노드를 가지는 rb 트리는 최대 2log(n+1)의 높이를 가진다 (증명: CLRS p.312))
때문에 탐색 시간 복잡도 역시 O(log n) 이내로 제한된다.
밑에서 배우겠지만, 트리의 균형을 잡기 위한 재조정 과정(color flip, rotation)이 필요한데, 이것들은 트리를 순회하거나 탐색하는 행위가 아니라 포인터를 재배치하고, 인덱스에 접근하여 색을 바꿔주는 것에 불과하므로 각 연산마다 O(1)의 시간복잡도만이 소요된다.
때문에 매우 적은 비용으로 트리 균형을 유지할 수 있고, 그 덕분에 예측가능하며 매우 효율적인 탐색 자료구조를 얻게된다.
RB 트리, 즉 레드-블랙 트리는 자기 균형을 유지하는 이진 탐색 트리의 한 종류로서, 각 노드에 추가적인 색상 정보(빨간색 또는 검은색)를 저장하여 트리의 균형을 유지한다. 이 색상 정보는 트리의 연산 시 균형을 맞추는 데 중요한 역할을 하며, 이를 통해 트리가 효율적으로 균형을 유지하도록 돕는다.
RB 트리는 다음 5가지 주요 속성을 만족해야 한다:
위 5번 조건에서 나오는 특별한 특징이 있다.
Q. 그런데, 5번 속성은 그대로 만족하지만 4번 속성에 대해선 어긋나게 될 수도 있는 거 아니야?
맞음. 그래서 뒤에 나오겠지만 연산을 수행할 때 5번 속성을 만족하면서 4번 속성을 유지하기 위해 추가적인 조치가 동반됨.
-> 색상 교환과 함께 회전, 색상 재배치 작업을 수행
4번과 5번 속성이 RB 트리 균형의 핵심 키이다. 때문에, 삽입 삭제를 할 때 4번과 5번 속성 유지에 초점을 맞춰서 조치를 취하게 된다.
방금 의문을 품었던 것과 같이 중간에 새로운 노드를 추가하거나 기존 노드를 삭제하면 기존 속성이 무너지게 되는데, 이들을 해결하려고 구조를 바꾸다보면 자연스럽게 트리의 균형이 잡힌다.
그럼 어떻게 위 속성을 유지할까? RB 트리는 다음과 같은 규칙을 통해 항상 자신의 상태를 진단하고 개선한다.
색깔 변경의 규칙
이제 각 연산에 대해 자세한 동작 원리를 살펴보자.
RB트리의 삽입 연산은 [삽입 -> RB 속성 검사 -> 재조정]의 단계를 거친다. (삽입 전에는 RB 트리 속성이 만족한 상태임을 가정한다.)
1번은 당연히 만족되는 거고, 이렇게 하면 2~4번 속성까지 모두 만족이 된다.
근데 1, 2, 3, 5 번에 대해서는 OK.
문제는, '색 변경과 회전을 통해 4번 속성을 복구' 하는 걸 어떻게 하느냐??
사실상 이게 본론이다.
삽입 후에 4번 속성에서 문제가 생기는 경우는 3가지 케이스이다.
case 2와 3의 경우 해결법을 간단 요약하면 다음과 같다.
case 3)
"왼쪽-왼쪽" 케이스: 할아버지, 부모 색 바꾸기 -> 우회전
"오른쪽-오른쪽" 케이스: 할아버지, 부모 색 바꾸기 -> 좌회전
case 2)
"왼쪽-오른쪽" 케이스: 부모 기준 좌회전 -> 할아버지, 부모 색 바꾸기 -> 할아버지 기준 우회전
"오른쪽-왼쪽" 케이스: 부모 기준 우회전 -> 할아버지, 부모 색 바꾸기 -> 할아버지 기준 좌회전
('할아버지 -> 부모'-'부모 -> 자식'의 방향을 뜻함)
이 중에서 가장 간단한 case3의 해결법부터 보자.
case 3 해결 방법
우회전에 대한 설명에서 왼쪽과 오른쪽을 뒤바꾸면 그대로 좌회전이 된다.
이때 할아버지 노드가 부모 노드의 자식 노드가 되기 때문에, 부모의 기존 자식 노드와의 교통정리가 필요하다.
이는 어느 방향으로 회전했는지에 따라 케이스가 달라진다.
1. 좌회전 이후
2. 우회전 이후
case 2 해결 방법
case 1 해결 방법
왜 이렇게 해결하냐면, case 1의 경우 해결 원리가 다음과 같기 때문이다.
우선 case 1 상황에서는 회전을 통해 조정하는 case 3, 2의 방식이 먹히지 않는다.
대신에, 위에서 봤던 5번 속성의 파생 특징 (RB 트리가 5번 속성을 만족하고, 두 자녀가 같은 색을 가질 때, 부모와 두 자녀의 색을 뒤바꿔도 5번 속성은 여전히 만족한다)를 활용한다.
이를 통해서 다음과 같이 교통 정리를 해준다.
이렇게 하면 1~5까지의 모든 속성을 만족하게 된다. (black은 연속되어도 괜찮다.)
이 내용을 알고리즘적으로 구현한게 위 해결방법이다.
Q. 다음과 같은 RB 트리가 있다. 80 -> 40 -> 35 -> 25 순으로 삽입 연산을 수행하시오.
1. insert(80)
2. insert(40)
3. insert(35)
4. insert(25)
참고 자료:
RB 트리 이해에 다음 영상이 많이 참고가 되었고, 시각화 자료도 워낙 뛰어나게 준비하셔서 그대로 가져온 부분이 많다.
https://www.youtube.com/watch?v=2MdsebfJOyM&t=6s
Red-Black Tree (RB 트리) | 삭제 연산 (0) | 2023.11.06 |
---|---|
BST 기반 자료구조들의 우선순위 (0) | 2023.11.05 |
자료구조 | 힙(Heap), 우선순위 큐(Priority Queue) (0) | 2023.10.26 |
자료구조 | 유니온-파인드(Union-Find) (1) | 2023.10.23 |
자료구조 | 신장 트리(ST), 최소 신장 트리(MST) (1) | 2023.10.22 |