[ 자료구조 ] 탐색 - 04 - 1 . 레드 블랙 트리 <회전 , 삽입>
[ 레드 블랙 트리 ]
[ 레드 블랙 트리 ]
이진 탐색 트리는 균형있게 자라지 못하는 경우가 대부분이다 . 이런 성장은 검색 효율을 심각하게 저하시킨다 .
레드 블랙 트리는 이를 해결하기 위한 이진 탐색 트리이다 .
해당 트리를 이해하는데 해당 사이트가 많은 도움이 되었다 . 참고하면서 공부하면 좋을것이다 .
레드 블랙 트리 시물레이터
이진 탐색 트리와 다른 점은 노드를 빨강 , 혹은 검정으로 표시한다는 점이다 .
이를 구현한 구조체는 다음과 같다 .
typedef struct tagRBTNode
{
struct tagRBTNode* Parent;
struct tagRBTNode* Left;
struct tagRBTNode* Right;
enum { Red , Black } Color;
ElementType Data;
}RBTNode;
[ 균형 유지의 비결 ]
레드 블랙 트리는 다음 규칙을 지킴으로써 균형을 유지한다 .

- 모든 노드는 검정색 아니면 빨간색이다 .
- 루트 노드는 검은색이다 .
- 잎 노드는 검은색이다 .
- 빨간 노드의 자식들은 모두 검은색이다 . 하지만 검은 노드의 자식이 빨간색일 필요는 없다 .
- 루트 노드부터 모든 잎 노드 사이에 있는 검은색 노드의 수는 모두 동일하다 .
NIL 노드는 아무 데이터도 가지고 있지 않은 검은색 더미노드이다 .
이는 레드 블랙트리의 구현을 쉽게 하기 위해 사용한다 .
원래의 잎 노드의 색에 구애받지 않고 "잎노드는 검은색이다" 의 규칙을 지킬 수 있기 때문이다 .
[ 기본 연산 - 회전 ]
[ 회전 ]
회전은 레드 블랙 트리 연산의 기초 초식으로 , 부모 - 자식 노드의 위치를 바꾸는 연산이다 .
방향에 따라 우회전 (왼쪽 자식과 부모의 교체)과 좌회전 (오른쪽 자식과 부모의 교체)으로 나뉜다 .

그냥 바꾸는 것으로는 이진트리의 규칙인 왼쪽자식은 나보다 작고 오른쪽자식은 크다를 어기기에 다음 처리가 필요하다.
=>우회전시 왼쪽 자식노드의 오른쪽 자식 노드를 부모의 왼쪽 자식으로 연결한다 .
=>좌회전시 오른쪽 자식노드의 왼쪽 자식노드를 부모의 오른쪽 자식으로 연결한다 .
[ 구현 - 좌회전 ]
void RBT_RotateRight (RBTNode ** Root , RBTNode* Parent)
{
RBTNode * LeftChld = Parent->Left ;
//왼쪽 자식노드의 오른쪽 자식을 부모의 왼쪽 자식으로 등록한다.
Parent ->Left = LeftChild->Right;
//왼쪽 자식노드의 오른쪽 자식이 Nil (더미노드)가 아니라면 부모를 등록해준다
if(LeftChld->Right != Nil )
LeftChild->Right->Parent = Parent;
//왼쪽 자식의 부모는 부모의 부모로 등록
LeftChild->Parent = Parent -> Parent;
//루트 노드라면 루트노드를 왼쪽 자식으로 변경한다 .
if(Parent->Parent==NULL)
(*Root) = LeftChild;
//루트 노드가 아니라면
else
{
//왼쪽 자식 노드를 부모가 있던 곳으로 위치시킨다 (할아버지의 자식 노드)
//부모가 왼쪽 노드였다면
if(Parent == Parent -> Parent -> Left)
Parent->Parent->Left = LeftChild;
//부모가 우측 노드였다면
else
Parent->Parent->Right = LeftChild;
}
}
[ 기본 연산 - 노드 삽입 ]
[ 삽입 ]
이진 탐색을 통해 삽입할 장소를 찾고 새 노드를 자식노드로 연결한다는 점은 이진 탐색 트리와 비슷하지만
레드 블랙 트리는 삽입 이후 무너졌을 규칙들을 살펴야 한다 .
레드 블랙 트리에 새 노드가 삽입되면 기본적으로 빨간색으로 칠하고 , 양쪽 자식에 NIL노드를 연결한다 .
해당 과정은 다음과 같다 .
- 이진 탐색을 통해 새 노드를 트리에 삽입한다 .
- 노드를 빨간색으로 칠하고 양쪽 자식에 NIL 노드를 연결한다 .
- 무너진 레드 블랙 트리의 규칙을 복구한다 .
이때 , 연결하는 NIL노드는 한개만 전역으로 생성한 후 새 노드를 생성할때마다 동일한 NIL 노드를 사용한다 .
[ 무너진 규칙의 복구 ]
무너진 규칙의 복구를 위해 레드 블랙 트리의 규칙을 다시 떠올리자면
- 모든 노드는 검정색 아니면 빨간색이다 .
- 루트 노드는 검은색이다 .
- 잎 노드는 검은색이다 .
- 빨간 노드의 자식들은 모두 검은색이다 . 하지만 검은 노드의 자식이 빨간색일 필요는 없다 .
- 루트 노드부터 모든 잎 노드 사이에 있는 검은색 노드의 수는 모두 동일하다 .
인데 , 삽입의 상황에서 신경써야 하는 규칙은 4번 . "빨간 노드의 자식들은 모두 검은색이다" 이다 .
즉 , 해당 규칙 위반 상황은 부모 노드의 색이 빨간색이라는 것을 의미한다 . (삽입 노드는 빨간색이기에)
이 경우 , 부모의 형제노드인 삼촌 노드에 따라 3가지 상황으로 다시 나뉜다 .
- 삼촌이 빨간색인경우
- 삼촌이 검은색인 경우 - 새로 삽입한 노드가 부모노드의 오른쪽 자식인 경우
- 삼촌이 검은색인 경우 - 새로 삽입한 노드가 부모노드의 왼쪽 자식인 경우
해당 상황의 해결을 위해 삼촌이 빨간색인 경우 색상의 변환을/ 삼촌이 검정색일 경우 회전을 수행한다.
[ 규칙의 복구 - 삼촌이 빨간색일경우 ]

해당 경우 부모와 삼촌을 검은색으로 칠한다 (5번 규칙을 위해 삼촌도 칠한다) . 이후 할아버지 노드를 빨간색으로 칠한다 .
할아버지 노드를 빨간색으로 칠했기에 해당 작업으로 생긴 부작용을 다시 살펴봐야 한다 .
할아버지 노드를 새로 삽입한 노드로 간주하여 할아버지의 할아버지가 빨간색이라면 위의 3가지 상황을 다시 봐야한다 .
해당 확인은 부모노드가 검은색이거나 , 새로 삽입한 ( 혹은 간주하는 ) 노드가 루트 노드일때 끝이 난다 .
[ 규칙의 복구 - 삼촌이 검정색이며 새로 삽입한 노드가 부모노드의 오른쪽 자식인 경우 ]

해당 경우 부모노드를 좌회전 (오른쪽 자식과 부모의 위치를 교환)한다 .
이는 " 3 . 삼촌이검은색인 경우 - 새로 삽입한 노드가 부모노드의 왼쪽 자식인 경우" 로 문제의 유형을 바꾼 것이다 .
이후 부모노드를 새로 삽입한 노드로 간주하고 세번째 상황으로 넘어간다 . (해당 이미지에서는 B)
[ 규칙의 복구 - 삼촌이 검정색이며 새로 삽입한 노드가 부모노드의 왼쪽 자식인 경우 ]

해당 경우 부모노드를 검은색 , 할아버지 노드를 빨간색으로 칠한 후
할아버지 노드를 우회전(왼쪽 자식과 부모의 위치를 교환) 한다 .
여기까지의 적용으로 4번 규칙을 위배하지 않게 된다 .
[ 구현 - 삽입 ]
void RBT_ReBuildAcfterInsert(RBTNode** Root , RBTNode* X)
{
//4번 규칙의 위반인 경우 계속 반복한다 .
while (X != (*Root)&& X->Parent->Color == Red)
{
//부모노드가 할아버지노드의 왼쪽 자식인 경우
if(X->Parent==X->Parent->Parent->Left)
{
//삼촌은 할아버지노드의 오른쪽 자식
RBTNode* Uncle =Parent->Parent->Right;
//삼촌이 검정색일 경우 회전
if(Uncle->Color==Black)
{
//삽입한 노드가 부모의 오른쪽 노드일때
if(X==X->Parent->Right)
{
X=X->Parent;
//좌회전
RBT_RotateLeft(Root,X);
}
//부모의 색은 검정
X->Parent->Color=Black;
//조부모의 색은 빨간
X->Parent->Parent->Color=Red;
//우회전
RBT_RotateRight(Root,X);
}
//삼촌이 빨간색일 경우 색의 변경
else
{
//부모와 삼촌은 검정
X->Parent->Color= Black;
Uncle->Color=Black;
//조부모는 빨간
X->Parent->Parent->Color=Red;
}
}
//부모노드가 할아버지 노드의 오른쪽 자식인 경우
else
{
//삼촌은 할아버지노드의 왼쪽 자식
RBTNode* Uncle =Parent->Parent->Left;
//삼촌이 검정색일 경우 회전
if(Uncle->Color==Black)
{
//삽입한 노드가 부모의 오른쪽 노드일때
if(X==X->Parent->Right)
{
X=X->Parent;
//좌회전
RBT_RotateLeft(Root,X);
}
//부모의 색은 검정
X->Parent->Color=Black;
//조부모의 색은 빨간
X->Parent->Parent->Color=Red;
//우회전
RBT_RotateRight(Root,X);
}
//삼촌이 빨간색일 경우 색의 변경
else
{
//부모와 삼촌은 검정
X->Parent->Color= Black;
Uncle->Color=Black;
//조부모는 빨간
X->Parent->Parent->Color=Red;
}
}
//루트는 항상 검은색
(*Root)->Color = Black;
}
}