모든 C언어 코드들은 정상 구현됩니다!
포스팅을 읽기에 앞서... 굉장히 구현하기도 이해하기도 까다로운 트리라고 생각됩니다. 그래서 해당 포스팅 내용이 완벽하다곤 생각하지 않습니다. 참고하여 주시고 읽어주시면 감사드리겠습니다.
수정해야될 부분이 있다면 계속 수정할 예정입니다.
[참고 사이트]
https://velog.io/@kku64r/rbtree
https://namu.wiki/w/%EB%A0%88%EB%93%9C-%EB%B8%94%EB%9E%99%20%ED%8A%B8%EB%A6%AC
https://blogshine.tistory.com/102
https://dev-game-standalone.tistory.com/92
회전관련: https://velog.io/@prkty/AVL-%ED%8A%B8%EB%A6%AC-vjg219mw

RB 트리는 이진트리에서 자식들이 한쪽으로 치우치는 것을 막기 위한 균형기능이 들어간 자가 균형 이진 트리이다. 자가 균형 이진트리는 대표적으로 전에 배웠던 자식의 Depth를 이용하는 AVL 트리와, 노드의 색을 이용하는 Red Black Tree 등이 있다.
이진 탐색 트리의 경우 균형이 맞지 않으면, 최악의 경우 시간 복잡도가 O(N)이다.
→ 그러나, RB Tree는 삽입, 삭제 동안 트리의 모양이 균형 잡히도록 각 노드들은 Red 혹은 Black의 색상을 가지고 모든 경우에서 O(logN)의 시간 복잡도를 보장받는다.
위의 사용 조건을 무조건 따라야한다.
그리고 삽입이나 삭제 등 해당 과정을 수행하기전 RB Tree는 모든 조건을 통과한 트리 형태임을 꼭 기억하자.

먼저 삽입을 진행하기 전에 삽입을 할 때 지켜져야하는 규칙을 살펴보겠다.


RB Tree에 3번을 Red로 초기화 하여 삽입한다.
이럴때, Double Red가 발생하고 삼촌의 색의 따라 두가지 방식을 사용할 수 있습니다.

본인 노드인 새로 삽입한 노드는 N, 부모 노드를 P, 조부모를 G, 부모P의 형제를 삼촌(U)이라고 합니다.
만약에 Double Red를 발생했을 때,
삼촌 노드가 빨간색이라면, Recoloring을 수행합니다.
삼촌 노드가 검은색이라면, Restructing을 수행합니다.

삼촌이 빨간색이면 원래의 블랙 카운트가 1임을 유지하며 조건 4인 빨간색이 겹치지 않는다는 조건을 충족하기 위해 부모와 삼촌을 색을 바꾸고, 조부모의 색또한 바꾼다. 이후에 조부모는 무조건 검은색이어야하므로 검은색으로 바꿔야한다. 만약, 조부모 상단의 부모가 있다면 계속해서 색 변환을 진행해야한다.
여기서 기억해야되는건 4번 규칙이다. 검은색 노드는 2번 나와도 된다. 빨간색 노드가 2번 나오면 안 된다.


Recoloring은 간단해 보이지만 문제는 조부모 노드가 루트 노드가 아니면서, 조부모 노드가 또다시 Double Red 문제가 발생하는 경우이다. 이런 경우 1-2에 따라 Restructuring 혹은 Recoloring을 반복해서 실행한다.

위에 서술한 것처럼 다양한 케이스들이 있지만, 대표적으로 LR 케이스일 때 과정을 설명해보겠다.
(Case 2)
1. LR 케이스의 경우, LL케이스로 만들어주기 위해 새로 삽입된 노드에 대해 Left Rotation을 먼저 수행합니다.
1-1. 균형을 맞추고, 4번 규칙 Red 노드가 연속으로 나오는 문제를 해결을 위해 부모와 조부모의 색을 바꿉니다.
(Case 3)
1-2. 이후 5번 규칙 NIL의 Black Depth를 맞춰주기 위해 부모기준 Right Rotation을 수행합니다.
해당 과정에서 주로 기억해야되는 점은 LL과 RR 케이스로 만들기 위해 Rotation을 적절히 수행합니다.(Case 2)
4번 규칙과 5번 규칙을 지키기 위해 부모, 조부모의 색을 서로 바꾸고 Rotation을 수행해야된다는 점입니다.(Case 3)
색 변경을 하고 회전을 하는 이유는 4번의 빨간색이 겹치지 않는다를 색을 변경하면서, 5번의 NIL의 횟수를 유지하여야 하므로 회전을 통해 해결한다.
→ 결론적으로 삽입은 5가지의 RB-Tree의 규칙을 꼭 준수하며(부모가 Red일시 고려) 삼촌의 색상에 따라 Recoloring(Red)과 Restructing(Black)을 수행하면 됩니다.
[C언어 코드 구현]
삽입 코드는 크게 4가지의 함수가 필요합니다.
가 있습니다.
// 새 트리 생성 함수
rbtree *new_rbtree(void) {
// tree 구조체 동적 할당
rbtree *p = (rbtree *)calloc(1, sizeof(rbtree));
// NIL 노드 생성 및 초기화
node_t *nil = (node_t *)calloc(1, sizeof(node_t));
if (nil == NULL)
{
free(p);
return NULL;
}
nil -> color = RBTREE_BLACK; // NIL 노드는 항상 Black
nil->key = 0;
nil->parent = NULL;
nil->left = NULL;
nil->right = NULL;
// NIL 노드 생성 및 초기화
// tree의 nil과 root를 nil 노드로 설정 (tree가 빈 경우 root는 nil노드여야 한다.)
p -> nil = nil;
p -> root = nil;
return p;
}
// 왼쪽 회전
void left_rotation(rbtree *t, node_t *x) {
// x가 위에 있는 부모이고, y는 x의 오른쪽 아래의 자식이다.
node_t *y = x -> right; // x의 오른쪽 자식은 노드 y 이다.
x -> right = y -> left; // y의 왼쪽 자식을 x의 오른쪽 자식으로 회전한다.
if ( y -> left != t -> nil) { // y의 왼쪽 자식이 비어 있지 않은 경우
y -> left -> parent = x; // y의 왼쪽 자식의 부모는 x가 된다.
}
y -> parent = x -> parent; // x의 부모는 y의 부모가 된다.
// 여기까지 y를 x자리로 올리기 위해 y의 자식을 x의 자식으로 처리하고 y를 x의 부모로 대체하는 과정이다. (y에 자식이 있을때)
if (x -> parent == t -> nil) { // x의 부모가 t는 nil(없을때)일때, 즉 x가 루트일때
t -> root = y; // y가 트리의 루트가 된다.
}
else if (x == x -> parent -> left) { // x가 부모의 왼쪽 자식일때
x -> parent -> left = y; // x의 부모의 왼쪽 자식을 y로 대체
}
else { // 그외의 경우(x는 부모의 오른쪽 자식이었다
x -> parent -> right = y; // x의 부모의 오른쪽 자식을 y로 대체
}
y -> left = x; // x를 y의 왼쪽 자식으로 둔다
x -> parent = y; // y룰 x의 부모로 둔다
// 여기까지 x가 루트였으면 y를 루트로 두고, x가 부모가 있었으면 그 부모를 y의 부모로 바꾸는 과정이다.
// 이후 마지막 두 문장은 x와 y의 부모, 자식 관계를 재정립한다.
}
// 오른쪽 회전
void right_rotation(rbtree *t, node_t *x) {
// x가 위에 있는 부모이고, y는 x의 왼쪽 아래의 자식이다.
node_t *y = x -> left; // x의 왼쪽 자식은 노드 y 이다.
x -> left = y -> right; // y의 오른쪽 자식을 x의 왼쪽 자식으로 회전한다.
if ( y -> right != t -> nil) { // y의 오른쪽 자식이 비어 있지 않은 경우
y -> right -> parent = x; // y의 오른쪽 자식의 부모는 x가 된다.
}
y -> parent = x -> parent; // x의 부모는 y의 부모가 된다.
// 여기까지 y를 x자리로 올리기 위해 y의 자식을 x의 자식으로 처리하고 y를 x의 부모로 대체하는 과정이다. (y에 자식이 있을때)
if (x -> parent == t -> nil) { // x의 부모가 t는 nil(없을때)일때, 즉 x가 루트일때
t -> root = y; // y가 트리의 루트가 된다.
}
else if (x == x -> parent -> right) { // x가 부모의 오른쪽 자식일때
x -> parent -> right = y; // x의 부모의 오른쪽 자식을 y로 대체
}
else { // 그외의 경우(x는 부모의 오른쪽 자식이었다
x -> parent -> left = y; // x의 부모의 왼쪽 자식을 y로 대체
}
y -> right = x; // x를 y의 오른쪽 자식으로 둔다
x -> parent = y; // y룰 x의 부모로 둔다
// 여기까지 x가 루트였으면 y를 루트로 두고, x가 부모가 있었으면 그 부모를 y의 부모로 바꾸는 과정이다.
// 이후 마지막 두 문장은 x와 y의 부모, 자식 관계를 재정립한다.
}
(삽입과정에서 RB트리 규칙에 따라 수정)
// 삽입에서 넘긴 값을 토대로 수정 수행
void insert_fix(rbtree *t, node_t *new_node) {
node_t *uncle; // 삼촌을 정의
while (new_node -> parent -> color == RBTREE_RED) { // 삽입한 부모 노드의 색깔이 Red 일때까지 실행
// 부모가 왼쪽일때 과정
if (new_node->parent == new_node->parent->parent->left) { // 새 노드의 부모가 조부모의 왼쪽일때
uncle = new_node->parent->parent->right; // 삼촌은 새 노드의 조부모의 오른쪽 자식임
// Case 1
if (uncle -> color == RBTREE_RED) { // 삼촌의 색이 빨간색인 케이스
new_node -> parent -> color = RBTREE_BLACK; // 새 노드의 부모는 검은색으로 지정
uncle -> color = RBTREE_BLACK; // 삼촌 또한 검은색으로 지정
new_node -> parent -> parent -> color = RBTREE_RED; // 조부모는 빨간색으로 지정
new_node = new_node -> parent -> parent; // 조부모까진 완벽하니, 조부모를 새 노드로 지정하고 재확인
}
else {
if (new_node == new_node -> parent -> right) { // 새 노드가 부모의 오른쪽에 있을때
// Case 2
new_node = new_node -> parent; // 새 노드를 부모로 지정
left_rotation(t, new_node); // 새 노드의 부모를 기준으로 왼쪽 로테이션 돌림
}
// Case 3(위치는 바꿨으니 색을 바꿔줘야함)
new_node -> parent -> color = RBTREE_BLACK; // 새 노드의 부모를 검은색으로 지정
new_node -> parent -> parent -> color = RBTREE_RED; // 조부모는 빨간색으로 지정
right_rotation(t, new_node -> parent -> parent); // 조부모 기준으로 오른쪽 로테이션 돌림
}
}
// 위의 케이스와 다르게 부모가 오른쪽일때 과정
else {
uncle = new_node->parent->parent->left; // 삼촌은 새 노드의 조부모의 왼쪽 자식임
// Case 1
if (uncle -> color == RBTREE_RED) { // 삼촌의 색이 빨간색인 케이스
new_node -> parent -> color = RBTREE_BLACK; // 새 노드의 부모는 검은색으로 지정
uncle -> color = RBTREE_BLACK; // 삼촌 또한 검은색으로 지정
new_node -> parent -> parent -> color = RBTREE_RED; // 조부모는 빨간색으로 지정
new_node = new_node -> parent -> parent; // 조부모까진 완벽하니, 조부모를 새 노드로 지정하고 재확인
}
else {
if (new_node == new_node -> parent -> left) { // 새 노드가 부모의 오른쪽에 있을때
// Case 2
new_node = new_node -> parent; // 새 노드를 부모로 지정
right_rotation(t, new_node); // 새 노드의 부모를 기준으로 오른쪽 로테이션 돌림
}
// Case 3(위치는 바꿨으니 색을 바꿔줘야함)
new_node -> parent -> color = RBTREE_BLACK; // 새 노드의 부모를 검은색으로 지정
new_node -> parent -> parent -> color = RBTREE_RED; // 조부모는 빨간색으로 지정
left_rotation(t, new_node -> parent -> parent); // 조부모 기준으로 왼쪽 로테이션 돌림
}
}
}
t -> root -> color = RBTREE_BLACK; // 루트는 항상 검은색이어야하는 규칙(루트의 부모는 없기 때문에 색을 검은색으로 지정해도됨)
}
본격적인 삽입 함수
// 삽입
node_t *rbtree_insert(rbtree *t, const key_t key) {
// 책에서는 새 노드가 z라고 나오는데,
// 해당 코드에선 z가 new_node로, new_node에 이미 키가 삽입되어 있다.
// 노드생성
node_t *new_node = (node_t *)calloc(1, sizeof(node_t));
if (new_node == NULL) {
return NULL; // 또는 에러 메시지 출력 후 return
}
// 새로 추가할 노드 값 초기화
new_node -> key = key; // 새 노드안에 들어가는 값은 key
// new_node -> color = RBTREE_RED; // 무조건 새 노드 들어갈때 빨강 지정
// new_node -> left = t -> nil; // 새 노드의 왼쪽 자식 노드는 nil
// new_node -> right = t -> nil; // 새 노드의 오른쪽 자식 노드는 nil
node_t *x = t -> root; // x는 루트 노드에서 시작 (트리를 따라 내려감)
// 현재 탐색 중인 노드 x(비교 대상)
node_t *y = t -> nil; // y는 x의 부모를 기억할 변수 (초기에는 nil)
// y는 삽입될 위치의 부모 노드
// 삽입 시작
while (x != t -> nil) { // x가 nil일 때 까지 내려간다(경계 NIL에 도달할 때까지 내려간다)
y = x; // 초기에는 y는 x이다.
// 밑으로 내려가면서 새 노드를 삽입할 적당한 위치를 찾는다.
if (new_node -> key < x -> key) { // 새노드의 키(값)가 x노드의 키(값)보다 작을때
x = x -> left; // x노드는 x의 오른쪽에 온다
}
else { // 반대의 경우
x = x -> right; // x노드는 x의 오른쪽에 온다
}
}
new_node -> parent = y; // y는 새 노드의 부모님으로 지정
// 여기까지 탐색대상 x를 내리며 값이 작으면 왼쪽 자식을 탐색하고 크면 오른쪽 자식을 탐색한다.
if (y == t -> nil) { // y가 nil이면(트리에 아무것도 없었다)
t -> root = new_node; // 새 노드는 루트이다
}
else if (new_node -> key < y -> key) { // 새 노드의 키가 y(부모)의 키보다 작으면
y -> left = new_node; // 새 노드는 y의 왼쪽 자식에 온다
}
else { // 그외의 케이스, 새 노드가 y보다 크면
y -> right = new_node; // 새 노드는 y의 오른쪽 자식에 온다
}
// 새노드를 삽입하기 위해 부모 y를 기준으로 키값을 비교하여 자식값에 넣는다.
// 새노드에 왼쪽, 오른쪽 자식에 NIL을 추가, 새 노드는 빨강으로 지정
new_node -> left = t -> nil;
new_node -> right = t -> nil;
new_node -> color = RBTREE_RED;
// 수정 작업 수행(트리와 새 노드값을 넘김)
insert_fix(t, new_node);
return new_node;
}
삭제를 할 때 지켜져야하는 규칙을 살펴보겠다. 삽입보다 어려우니 RBtree 조건과 4가지 케이스를 유념하며 코드를 짜야한다.
노드 삭제 전 BST 방식으로 진행
삭제할 노드가 두 개의 자식을 가지고 있다면, 일반 BST처럼 후계자(successor) 또는 전임자(predecessor)로 교체 후 삭제합니다.
→ 실제 삭제는 리프 노드 또는 하나의 자식만 있는 노드에서 발생하게 됩니다.
삭제되는 노드가 빨강인 경우
삭제되는 노드가 검정인 경우

Case 1

w가 Red일 시, 자식들은 Black입니다. 이들은 NIL일수 없습니다. 만약, NIL이면 5번 조건이 충족되지 않기 때문입니다. x 더블 블랙을 없애는 것이 핵심이다.
→ w를 Black으로 변경해 준후, B를 Red로 변경합니다. D를 기준으로 left-roatation을 적용합니다. 기존의 w의 자식이었던 C는 B의 자식으로 편입됩니다.
Case 2

w가 Black일시, w의 자식들도 Black인 경우 입니다. 회색 노드는 Black / Red 둘다 가능합니다.
여기서 생각해야하는 것은 자식의 두개의 Black을 받아 부모가 Black이 될 수 있습니다.
→ x의 Black을 B에게 전달하고, w를 Red로 바꿉니다. B를 새로운 x로 지정합니다.
만약, Case1에서 이 경우에 도착했다면 x는 Red였고, 새로운 x는 Red와 Black이 되었습니다. 그러면 Black으로 변경하고 끝내면 됩니다. 사실상 B가 자식 두개의 Black을 받아서 Black이 됐습니다.
Case 3

w는 Black이고 w의 왼쪽자식이 Red일때, 안쪽에 있는 Red 자식을 회전을 통해 밖으로 빼줘야합니다.
→ w를 Red로 변경하고 w의 왼쪽 자식을 Black으로 변경합니다. 이후 w에 대해 right rotation을 적용합니다. x의 새로운 형제 w는 오른쪽 자식이 Red입니다. 이후는 Case 4를 통해 해결합니다.
Case 4

w는 Black이고 w의 오른자식이 Red일때, w와 B의 색을 교환합니다.(w의 색을 현재 B의 색인 Black으로 변경합니다.) w의 오른쪽 자식 E를 Black으로 변경합니다.
D에 대해 left rotation을 적용 후, x의 extra Black을 제거한 후 종료합니다.(Black Depth 유지)
+[직관적인 설명]
D의 Black 을 C와 E에 나누어줍니다. 그러면 D는 Red가 됩니다. C는 더블 블랙이되고, E는 블랙이 됩니다. 해당 상태에서 B와 D색을 서로 바꿔 left rotation을 해주면 B는 빨간색이되고 D가 원래의 B의 색(Red or Black)이 됩니다. 그러면 B의 왼쪽 자손인 본래의 더블 블랙과 C에서 온 더블 블랙이 만나 B가 Red에서 Black으로 바뀝니다. 그러면 Case4를 완료할 수 있습니다.
이에 따라 B가 어떤 색이든 상관없습니다.
→ 최종적으로 삭제는 두가지 케이스로 바꿔서 해결해야한다.
삽입 코드는 크게 4가지의 함수가 필요합니다.
// 삭제에서 노드가 하나의 자식만 가질 때, 두 자식을 가질때 후임자와 교체(부모와 후임자와 교체)
// u노드를 v로 대체함
void Transplant(rbtree *t, node_t * u, node_t *v) {
if (u -> parent == t -> nil) { // u의 부모가 NULL일때 (루트일때?)
t -> root = v; // v(후임자)가 트리의 루트이다
}
else if (u == u -> parent -> left) { // u가 u의 부모 왼쪽 자식과 같으면
u -> parent -> left = v; // u의 부모의 왼쪽 자식을 v로 대체
}
else { // u가 u의 부모 오른쪽 자식과 같으면
u -> parent -> right = v; // u의 부모의 오른쪽 자식을 v로 대체
}
v -> parent = u -> parent; // v의 부모는 u의 부모와 같다
}
// 특정노드 p의 후손을 찾음
node_t *minimum(rbtree *t, node_t *p) {
node_t *cur = p; // cur로 p의 후손을 찾는다.
while(cur -> left != t -> nil) { // cur 왼쪽 자식이 nil이 나올때까지 시행
cur = cur -> left; // cur을 왼쪽 자식으로 보낸다.
}
return cur; // cur을 리턴한다(erase로 보냄)
}
(삭제 과정에서 RB트리 규칙에 따라 수정)
// 지우는 과정에서의 RB 트리 규칙에 따라 수정
void erase_fix(rbtree *t, node_t *x) {
node_t *w = x; // 형제 노드를 가리키기 위한 포인터 초기화 (임시)
// x가 루트가 아니고, 검은색 노드일 때까지 반복 (균형 깨진 상태)
while (x != t->root && x->color == RBTREE_BLACK) {
// case: x가 부모의 왼쪽 자식일 경우
if (x == x->parent->left) {
w = x->parent->right; // 형제 노드 w는 부모의 오른쪽 자식
// case 1: 형제 w가 빨강일 때
if (w->color == RBTREE_RED) {
w->color = RBTREE_BLACK; // 형제를 검정으로
x->parent->color = RBTREE_RED; // 부모는 빨강으로
left_rotation(t, x->parent); // 부모를 기준으로 좌회전
w = x->parent->right; // 회전 후 w를 다시 지정
}
// case 2: 형제의 양쪽 자식이 모두 검정
if (w->left->color == RBTREE_BLACK && w->right->color == RBTREE_BLACK) {
w->color = RBTREE_RED; // 형제를 빨강으로 칠함
x = x->parent; // x를 부모로 올려서 fix 계속 진행
}
// case 3 or 4
else {
// case 3: 형제의 오른쪽 자식이 검정
if (w->right->color == RBTREE_BLACK) {
w->left->color = RBTREE_BLACK; // 형제의 왼쪽 자식을 검정으로
w->color = RBTREE_RED; // 형제를 빨강으로
right_rotation(t, w); // 형제를 기준으로 우회전
w = x->parent->right; // w 업데이트
}
// case 4: 형제의 오른쪽 자식이 빨강 (case 3을 거쳐 오기도 함)
w->color = x->parent->color; // 형제는 부모의 색을 물려받고
x->parent->color = RBTREE_BLACK; // 부모는 검정
w->right->color = RBTREE_BLACK; // 형제의 오른쪽 자식도 검정
left_rotation(t, x->parent); // 부모 기준으로 좌회전
x = t->root; // x를 루트로 설정하고 루프 종료
}
}
// case: x가 부모의 오른쪽 자식일 경우 (위와 좌우 반대)
else {
w = x->parent->left; // 형제 노드는 왼쪽 자식
// case 1: 형제가 빨강일 때
if (w->color == RBTREE_RED) {
w->color = RBTREE_BLACK;
x->parent->color = RBTREE_RED;
right_rotation(t, x->parent);
w = x->parent->left;
}
// case 2: 형제 자식 둘 다 검정
if (w->right->color == RBTREE_BLACK && w->left->color == RBTREE_BLACK) {
w->color = RBTREE_RED;
x = x->parent;
}
// case 3 or 4
else {
// case 3: 형제의 왼쪽 자식이 검정
if (w->left->color == RBTREE_BLACK) {
w->right->color = RBTREE_BLACK;
w->color = RBTREE_RED;
left_rotation(t, w);
w = x->parent->left;
}
// case 4
w->color = x->parent->color;
x->parent->color = RBTREE_BLACK;
w->left->color = RBTREE_BLACK;
right_rotation(t, x->parent);
x = t->root;
}
}
}
// 루프가 끝나면 x의 색을 검정으로 (속성 회복)
x->color = RBTREE_BLACK;
}
(본격적인 삭제함수)
// y: 실제로 삭제되거나 트리 구조에서 제거될 노드
// x: erase_fix()에서 사용할 삭제된 자리를 채우는 노드
// y_origin_color: 삭제 전 노드의 색을 기억 → 검정이면 erase_fix()가 필요함
int rbtree_erase(rbtree *t, node_t *p) {
node_t *y = p; // 추후에 p를 따로 쓰기 위해 y로 노드 인자 추가
node_t *x = p; // 추후에 p를 따로 쓰기 위해 x로 노드 인자 추가
color_t y_origin_color = y -> color; // y 컬러를 나중에 쓰기 위해 y_origin_color로 저장
if (p -> left == t -> nil) { // 기준p의 왼쪽 자식이 NIL이면 수행
x = p -> right; // x를 p의 오른쪽 자식이라고 지정
Transplant(t, p, p -> right); // p를 오른쪽 자식으로 바꾼다.
}
else if (p -> right == t -> nil) { // p의 오른쪽 자식인 NIL이라면
x = p -> left; // x를 p의 왼쪽자식으로 지정
Transplant(t, p, p -> left); // p를 왼쪽 자식으로 바꾼다.
}
else {
y = minimum(t,p -> right); // p를 오른쪽 자식으로 바꾼다.
y_origin_color = y -> color; // y의 원래 컬러를 저장한다
x = y -> right; // x는 y의 오른쪽 자식이다.
////////// 해당 부분 문제 //////
// if (y->parent == p)
// x->parent = y;
// else {
// Transplant(t, y, y->right);
// y->right = p->right;
// y->right->parent = y;
// }
// Transplant(t, p, y);
// y->left = p->left;
// y->left->parent = y;
// y->color = p->color;
// }
////////// 수정전 ////////
if (y != p -> right) { // y가 트리에서 더 아래쪽인가?
Transplant(t, y, y -> right); // y를 오른쪽 자식으로 바꾼다.
y -> right = p -> right;
y -> right -> parent = y;
}
else { // 해당 else문을 잘못 들여쓰기 했다.
x -> parent = y; // x가 NIL인 경우
}
Transplant(t, p, y); // p를 그 후손인 y로 바꾼다.
y -> left = p -> left; // z의 왼쪽 자식을 y에 부여한다.
y -> left -> parent = y; // 왼쪽 자식이 없는 y
y -> color = p -> color;
}
//////////////////////////////
if (y_origin_color == RBTREE_BLACK) { // 레드 블랙 위반이 발생한 경우
erase_fix(t, x); // 수정한다.
}
return 0;
}
정글에서 주어진 RB Tree에는 삽입, 삭제 뿐만 아니라 delete_rbtree(트리 전체 삭제), find, max, min, array 함수들을 추가로 구현해야했습니다.
구현 방식과 힌트들은 아래 코드의 주석을 참고하시면 좋을 것 같습니다.
// RB 트리는 자식과 부모가 연계되어 있으므로 노드 자체의 메모리해제를 따로 해야한다.
// 왼쪽 자식 -> 오른쪽 자식 -> 자기자신 순으로 해제한다.
void free_node(rbtree *t, node_t *del) {
if(del == t -> nil) { // 받은 노드가 NULL이면 그냥 리턴
return;
}
free_node(t, del -> left); // 왼쪽 자식 메모리 해제하기 위해 재귀
free_node(t, del -> right); // 오른쪽 자식 메모리 해제하기 위해 재귀
free(del); // 메모리 해제
}
// 모든 노드들 해제 후! NIL, RB 트리 구조체 해제
void delete_rbtree(rbtree *t) {
free_node(t, t -> root); // 루트를 시작점으로 모든 자식 노드를 순회하며 해제
free(t -> nil); // 공통의 NIL들 해제
free(t); // 트리 구조체 해제
}
// 노드 검색
node_t *rbtree_find(const rbtree *t, const key_t key) {
node_t *cur = t -> root; // cur은 루트부터 시작
while(cur != t -> nil) { // cur 현재 확인 중인 노드가 NIL 될때까지 실행
if(cur -> key == key) { // 현재 비교하는 cur과 키값이 같을때
return cur; // cur 반환
}
if(cur -> key < key) { // 지금 확인중인 cur보다 키값이 클때
cur = cur -> right; // 오른쪽 자식으로 검색
}
else {
cur = cur -> left; // cur보다 키값이 작을때, 왼쪽 자식으로 검색
}
}
if(cur == t -> nil) { // cur이 없을때(트리가 없을때)
return NULL; // NULL 반환
}
return cur; // 값 리턴
}
// 최솟값 탐색
node_t *rbtree_min(const rbtree *t) {
node_t *cur = t -> root; // cur은 루트부터 시작
if(cur == t -> nil){ // 먼저 트리에 아무것도 없을 예외상황을 처리해야한다
return NULL; // 밑에 while문에 포함시키면 RB트리에서 맨밑이 항상 NIL이라 항상 NULL 나옴
}
node_t *min = NULL;
while(cur != t -> nil) { // NIL 만나기 직전까지 진행
min = cur; // NIL 만나기전 현재 노드를 기억
cur = cur -> left; // 최솟값으로 left로만 가야함
}
return min; // 값 리턴
}
// 최댓값 탐색
node_t *rbtree_max(const rbtree *t) {
node_t *cur = t -> root; // cur은 루트부터 시작
if(cur == t -> nil){ // 먼저 트리에 아무것도 없을 예외상황을 처리해야한다
return NULL; // 밑에 while문에 포함시키면 RB트리에서 맨밑이 항상 NIL이라 항상 NULL 나옴
}
// 최댓값 검색
node_t *max = NULL;
while(cur != t -> nil) { // NIL 만나기 직전까지 진행
max = cur; // NIL 만나기전 현재 노드를 기억
cur = cur -> right; // 최댓값으로 right로만 가야함
}
return max; // 값 리턴
}
int rbtree_erase(rbtree *t, node_t *p) {
// TODO: implement erase
return 0;
}
// 키값이 주어진 n 크기까지의 배열의 개수를 출력(중위순회)
// 내부에서 쓰는 재귀함수로 실제 트리를 순회하며 데이터를 배열에 넣음
void to_array(const rbtree* t, key_t *arr, const size_t n, node_t* cur, int* count) {
if(*count >= n || cur == t -> nil) return;
// count는 지금까지 배열에 저장한 key의 개수, n은 배열에 담을 수 있는 최대 개수
// 그러므로count가 주어진 n보다 커지거나 같아지면 바로 리턴
// 맨마지막인 NIL까지 내려가서 순회 종료
// 중위순회
to_array(t, arr, n, cur -> left, count); // 왼쪽 자식 맨끝부터 시작
if(*count < n) { // n횟수만큼 카운트가 진행
arr[*count] = cur -> key; // 배열에 현재 진행중인 키를 저장
*count += 1; // 배열에 저장한 횟수 +1
}
to_array(t, arr, n, cur -> right, count); // 오른쪽을 순회하고 끝냄
}
// 사용자가 호출하는 함수 역할로 외부에선 해당 함수만 호출(트리, 배열, 사이즈 n만 호출해 사용)
int rbtree_to_array(const rbtree *t, key_t *arr, const size_t n) {
int count = 0; // count 0으로 초기화
to_array(t, arr, n, t -> root, &count);
// &count인 이유는 하단의 *count가 포인터로 인자를 줄려면 & 주소로 보내줘야한다.(값 변경을 반영)
return 0; // 값 리턴
}