
Q. 지금 지쳤나요?
A. 네

https://github.com/Anas-wg/jg_rbtree/blob/main/rbtree_lab/src/rbtree.c
rbtree_delete_fixup() 함수에서 형제 노드 bro가 t->nil인 경우를 처리하지 않음
while (eb != t->root && eb->color == RBTREE_BLACK) {
if(eb == t->nil){
break;
}
// 방향 체크
if(eb->parent->left == eb){
is_eb_left = 1;
bro = eb->parent->right;
} else {
is_eb_left = 0;
bro = eb->parent->left;
}
// CASE 1: DB형제 RED
if (bro->color == RBTREE_RED){
// DB 형제와 부모 노드 색교환
color_t temp = bro->color;
bro->color = bro->parent->color;
bro->parent->color = temp;
// 부모 노드 기준 회전
if(is_eb_left){
left_rotate(t, bro->parent);
bro = eb->parent->right;
} else {
right_rotate(t, bro->parent);
bro = eb->parent->left;
}
}
// CASE 2: 형제 BLACK + 자식 둘 다 BLACK
if ((bro->left == t->nil || bro->left->color == RBTREE_BLACK) &&
(bro->right == t->nil || bro->right->color == RBTREE_BLACK)){
bro->color = RBTREE_RED; // DB 형제노드 색교환
eb = eb->parent; // 부모에게 짬처리
}
지금 수정 전 Code를 보면 형제가 NIL이고, 이경우 BLACK 이 되어 CASE 2로 넘어가야 했다.
그치만 bro의 left, right만 확인했기 때문에 bro == nil 일때 제대로 동작하지 않았던 것!
while (eb != t->root && eb->color == RBTREE_BLACK) {
if(eb == t->nil){
break;
}
// 방향 체크
if(eb->parent->left == eb){
is_eb_left = 1;
bro = eb->parent->right;
} else {
is_eb_left = 0;
bro = eb->parent->left;
}
// ✅ nil 형제 방지
if (bro == t->nil) {
eb = eb->parent;
continue;
}
// CASE 1: DB형제 RED
if (bro->color == RBTREE_RED){
// DB 형제와 부모 노드 색교환
color_t temp = bro->color;
bro->color = bro->parent->color;
bro->parent->color = temp;
// 부모 노드 기준 회전
if(is_eb_left){
left_rotate(t, bro->parent);
bro = eb->parent->right;
} else {
right_rotate(t, bro->parent);
bro = eb->parent->left;
}
}
// CASE 2: 형제 BLACK + 자식 둘 다 BLACK
if ((bro->left == t->nil || bro->left->color == RBTREE_BLACK) &&
(bro->right == t->nil || bro->right->color == RBTREE_BLACK)){
bro->color = RBTREE_RED; // DB 형제노드 색교환
eb = eb->parent; // 부모에게 짬처리
}
bro가 t->nil인 경우에도 CASE 2 조건으로 들어가도록 명시적으로 처리함으로써, rbtree_find() 에서
반환되는 문제를 완전히 해결.
nil의 부모, 자식을 누군 초기화 하고 누군 초기화 안했는데 둘다 통과될까?
새로운 RB 트리를 정의하는 함수인데,
이때 nil 노드를 초기화 시, nil->left = nil->right = nil->parent = nil; 이 부분을
어떤 사람은 있고, 어떤 사람은 없는데 왜 둘다 되지? 라는 궁금증이 발생
rbtree *new_rbtree(void) {
rbtree *t = (rbtree *)calloc(1, sizeof(rbtree)); // 동적 메모리 할당
// TODO: initialize struct if needed
// nil 노드 생성 & 초기화
node_t *nil = (node_t *)calloc(1, sizeof(node_t));
// nil 은 black
nil->color = RBTREE_BLACK;
// 초기상태 root노드 없으니까 nil
nil->left = nil->right = nil->parent = nil;
t->root = nil;
t->nil = nil; // 이걸 안 하면 NULL 접근되거나 쓰레기 값 뜰 수 있음
return t;
}
둘다 되는 게 맞았다.
원인은 로직 자체의 차이!
if (x != t->nil && x->parent->left == x) { ... } // 항상 nil인지 먼저 체크
이렇게 nil 에 접근시 조건문에 진입하지 않도록 로직을 구현하면
nil의 자식에도 접근할 일이 없기 때문에 초기화하지 않아도 코드가 돌아갔던 것.
// nil->left = nil->right = nil->parent = nil;
if (x->parent->left == x){
is_eb_left = 1;
bro = x->parent->right;
} else {
is_eb_left = 0;
bro = x->parent->left;
}
조건문에서 nil 검사를 하지 않기 때문에 x가 nil 이라면 nil->parent에 접근하게 된다.
이때 nil->parent = nil 을 위에서 방어적으로 초기화 해줬다면
문제없이 로직이 동작한다.
Ik와 Ik+1이 메모리상 나란히 존재.setjmp, longjmp)
이게 삽질이지... 그래도 해결했으니까 한잔해~~~
으악 완성 축하드립니다 ~!! (부럽)