RB-Tree 구현하기 (C언어) - Part.4 삽입 구현
전 포스팅에서 RB-Tree에서의 삽입에 대한 이론적인 설명을 했다. 기본적으로 RB트리도 이진 탐색트리의 일종이기 때문에 삽입에서의 과정은 기존의 이진 탐색트리와 동일하다고 할 수 있다. 이제 다만 삽입 이후 RB-Tree의 조건들을 위반했을 경우 이를 고쳐나가는 fixup 과정이 추가되었다는 것이다.
이 전 글을 보면 RB-Tree에서 fixup을 하는 경우는 크게 3가지로 얘기했다. Case1 ~ 3까지의 경우를 확인하며 이를 수행하는 코드를 작성해 볼 것이다.
2025.04.21 - [크래프톤 정글] - RB-Tree 구현하기 (C언어) - Part.3 삽입 이론 설명
RB-Tree 구현하기 (C언어) - Part.3 삽입 이론 설명
RB-Tree의 주요 속성 (5가지 규칙)모든 노드는 레드 또는 블랙이다.루트 노드는 항상 블랙이다.모든 리프 노드(NIL 노드)는 블랙이다.레드 노드의 자식은 반드시 블랙이다.→ 즉, Red 노드는 연속해서
www.gowoong.com
1. 구현 목표
RB-Tree의 삽입을 구현하기 위해 크게 4가지 기능을 구현해야 할 것으로 예상 된다.
- 삽입 함수 (BST의 삽입을 구현)
- fixup 함수 (RB-Tree의 속성을 위반했을 경우 이를 고치기 위함)
- 왼쪽 회전
- 오른쪽 회전
이제 구현에 들어가 보겠다.
2. 기능 구현
🔨 새로운 값 삽입하기
node_t *rbtree_insert(rbtree *t, const key_t key) {
// TODO: implement insert
return t->root;
}
이 번에도 이전 글들과 같이 구현 코드를 바로 설명하지 않고 어떻게 구현을 해 나가야 할지 구현 아이디어를 먼저 소개하겠다.
구현 아이디어:
- 새로 삽입할 노드를 선언하고 동적 메모리 할당을 통해 생성한다.
- 생성된 새로운 노드는 RB-Tree의 속성에 맞도록 초기화한다.
- 색상은 RED로 시작
- 노드가 가지는 부모, 자식들은 nil로 초기화
- 현재 노드를 나타내는 cur 선언
- 현재 노드가 nil이 될 때까지 반복문을 수행한다.
- BST의 삽입과 같이 현재 노드의 키 보다 새로 삽입되는 키 값이 작으면 왼쪽 크면 오른쪽 노드를 현재 노드로 설정한다.
- 모든 반복이 끝이 나면 (삽입할 위치 결정)
- 새로운 노드의 부모 노드를 현재 노드로 설정한다.
- 만약 현재 노드가 nil이었다면(최초 삽입의 경우) 그냥 노드를 삽입한다.
- BST 삽입은 완료했으나 RB-Tree의 속성을 만족하는지 확인하고 수정하기 위해 fixup 함수를 호출한다.
- 수정이 완료된 트리의 root를 반환
node_t *rbtree_insert(rbtree *t, const key_t key) {
// TODO: implement insert
node_t *new_node = (node_t *)calloc(1, sizeof(node_t));
if (new_node == NULL) {
return NULL;
}
new_node->key = key;
new_node->color =RBTREE_RED;
new_node->left = t->nil;
new_node->right = t->nil;
new_node->parent = t->nil;
node_t *cur = t->root;
while (t->nil != cur) {
if (key < cur->key) {
if (cur->left == t->nil) {
cur->left = new_node;
break;
}
cur = cur->left;
}else {
if (cur->right == t->nil) {
cur->right = new_node;
break;
}
cur = cur->right;
}
}
new_node->parent = cur;
if (cur == t->nil) {
t->root = new_node;
}
// RB tree 케이스 처리 함수......
rb_insert_fixup(t, new_node);
return t->root;
}
더 보기를 통해 구현한 코드를 확인할 수 있다.
🔨 삽입 후 수정하기
void rb_insert_fixup(rbtree *t, node_t *target) {
}
fixup함수는 새로 삽입했던 노드를 기준으로 RB-Tree의 속성 위반을 고쳐나가는 함수이다.
구현 아이디어:
- 반복문 while 사용 - 삽입하는 노드의 부모 노드의 색깔이 RED인 경우 반복 계속
- 편리하게 작성하기 위해 parent, grandparent라는 변수 선언
- 조건문 if (부모 노드가 할아버지 노드의 왼쪽 자식인 경우)
- 삼촌 uncle 노드를 선언하고 해당되는 값을 설정
- 조건문 if ( 삼촌의 색깔이 RED 인 경우) -> case 1
- 부모의 색상을 black으로 바꾸고 삼촌의 색상도 black으로 바꾼다. 반면 할아버지의 색상을 RED로 바꾼다.
- 할아버지 노드를 새로운 타깃으로 설정한다.
- 조건문 else (삼촌의 색깔이 Black인 경우)
- 조건문 if (본인- target 이 부모의 오른쪽 자녀인 경우) -> case 3
- 부모를 대상으로 설정한다.
- 부모를 기준으로 왼쪽으로 회전한다.
- 할아버지를 새로운 부모로 설정한다. -> 회전으로 현재 부모의 위치가 변경되었기 때문
- 부모의 색깔을 Black으로 설정한다.
- 할아버지의 색깔을 RED로 바꾼다.
- 할아버지 노드를 기준으로 오른쪽으로 회전한다. -> case 2
- 조건문 if (본인- target 이 부모의 오른쪽 자녀인 경우) -> case 3
- 조건문 else (부모 노드가 할아버지 노드의 오른쪽 자식인 경우)
- 위의 조건을 반대로 하여 작성한다.
- while 반복이 종료되면 즉 더 이상 수정할 노드가 없거나 노드가 루트 노드에 도달한 경우 root 노드의 색깔을 Black으로 설정한다.
더 보기를 통해 구현된 코드를 확인할 수 있다.
void rb_insert_fixup(rbtree *t, node_t *target) {
while (target->parent->color == RBTREE_RED) {
node_t *parent = target->parent;
node_t *grandparent = parent->parent;
if (grandparent->left == parent) {
// 부모 노드가 할아버지 노드의 왼쪽 자식일 경우
node_t *uncle = grandparent->right; // 삼촌 노드
if (uncle->color == RBTREE_RED) { //case 1
parent->color = RBTREE_BLACK;
uncle->color = RBTREE_BLACK;
grandparent->color = RBTREE_RED;
target = grandparent;
} else{
if (target == parent->right) {
// 부모가 오른쪽 자식이면 left-rotation
target = parent;
rb_left_rotation(t, target);
parent = target->parent;
}
parent->color = RBTREE_BLACK;
grandparent->color = RBTREE_RED;
//right_rotation
rb_right_rotation(t, grandparent);
}
}
else {
// 부모 노드가 할아버지 노드의 오른쪽 자식일 경우
node_t *uncle = grandparent->left; // 삼촌 노드
if (uncle->color == RBTREE_RED) { //case 1
parent->color = RBTREE_BLACK;
uncle->color = RBTREE_BLACK;
grandparent->color = RBTREE_RED;
target = grandparent;
} else{
if (target == parent->left) {
// 부모가 왼쪽 자식이면 right-rotation
target = parent;
//right_rotation
rb_right_rotation(t, target);
parent = target->parent;
}
parent->color = RBTREE_BLACK;
grandparent->color = RBTREE_RED;
//left_rotation
rb_left_rotation(t, grandparent);
}
}
}
t->root->color = RBTREE_BLACK;
}
🔨 왼쪽 회전
왼쪽 회전을 구현하겠다.
void rb_left_rotation(rbtree *t, node_t *pivot) {
}
구현 아이디어 :
- 왼쪽 회전의 경우 부모 노드와 그 오른쪽 자녀에 대해서 수정이 일어나기 때문에 right_child라는 변수를 선언하고 pivot 노드의 오른쪽 자식을 설정
- 회전을 수행할 때 right_child의 왼쪽 자식들 즉 베타에 있는 자식들을 피봇 대상 노드 X의 오른쪽 자식으로 연결한다.
- 조건문 if (right_child)의 왼쪽 자식이 nil이 아닌 경우
- nil노드가 아닌 경우 pivot의 오른쪽에 연결하기 위해 현재 선택한 노드의 부모를 pivot으로 바꾼다.
- 기존 노드 X의 부모를 Y 노드의 부모로 설정한다.
- 이때 X의 부모가 nil이었을 경우 즉 X가 루트 노드였을 경우 Y노드를 새로운 루트 노드로 설정한다.
- 조건문 else if (피봇 되는 노드가 피봇 노드 부모의 왼쪽 자식인 경우)
- 피봇 되는 노드의 부모 노드의 왼쪽 자식을 right_child로 재 조정
- 조건문 else (피봇되는 노드가 피봇 노드 부모의 오른쪽 자식인 경우)
- 파봇 되는 노드의 부모 노드의 오른쪽 자식을 right_child로 재 조정
- right_child의 왼쪽 자식을 pivot으로 설정
- 피봇 노드의 부모를 right_child로 설정한다.
void rb_left_rotation(rbtree *t, node_t *pivot) {
node_t *right_child = pivot->right;
pivot->right = right_child->left;
if (right_child->left != t->nil) {
right_child->left->parent = pivot;
}
right_child->parent = pivot->parent;
if (pivot->parent == t->nil) {
t->root = right_child;
}
else if (pivot == pivot->parent->left) {
pivot->parent->left = right_child;
}
else {
pivot->parent->right = right_child;
}
right_child->left = pivot;
pivot->parent = right_child;
}
완성 코드는 더 보기를 통해 확인할 수 있다.
🔨 오른쪽 회전
왼쪽 회전을 구현하겠다.
void rb_right_rotation(rbtree *t, node_t *pivot) {
}
구현 아이디어 :
- 오른쪽 회전의 경우 부모 노드와 그 왼쪽 자녀에 대해서 수정이 일어나기 때문에 left_child라는 변수를 선언하고 pivot 노드의 왼쪽 자식을 설정
- 회전을 수행할 때 left_child의 오른쪽 자식들 즉 베타에 있는 자식들을 피봇 대상 노드 X의 왼쪽 자식으로 연결한다.
- 조건문 if (left_child)의 오른쪽 자식이 nil이 아닌 경우
- nil노드가 아닌 경우 pivot의 왼쪽에 연결하기 위해 현재 선택한 노드의 부모를 pivot으로 바꾼다.
- 기존 노드 X의 부모를 Y 노드의 부모로 설정한다.
- 이때 X의 부모가 nil이었을 경우 즉 X가 루트 노드였을 경우 Y노드를 새로운 루트 노드로 설정한다.
- 조건문 else if (피봇 되는 노드가 피봇 노드 부모의 왼쪽 자식인 경우)
- 피봇 되는 노드의 부모 노드의 왼쪽 자식을 left_child로 재 조정
- 조건문 else (피봇되는 노드가 피봇 노드 부모의 오른쪽 자식인 경우)
- 파봇 되는 노드의 부모 노드의 오른쪽 자식을 left_child로 재 조정
- left_child의 오른쪽 자식을 pivot으로 설정
- 피봇 노드의 부모를 left_child로 설정한다.
void rb_right_rotation(rbtree *t, node_t *pivot) {
node_t *left_child = pivot->left;
pivot->left = left_child->right;
if (left_child->right != t->nil){
left_child->right->parent = pivot;
}
left_child->parent = pivot->parent;
if (pivot->parent == t->nil){
t->root = left_child;
}
else if (pivot == pivot->parent->right){
pivot->parent->right = left_child;
}
else{
pivot->parent->left = left_child;
}
left_child->right = pivot;
pivot->parent = left_child;
}
완성 코드는 더 보기를 통해 확인할 수 있다.