개요
일반 이진 탐색 트리의 경우 균형이 맞지 않을 수 있다. 이를 해결하기 위한 알고리즘이 레드 블랙 트리이다.
특징
이진 탐색 트리의 특징에 더해서 레드 블랙 트리만의 특징들이 있다.
- 노드는 레드 혹은 블랙 중의 하나이다.
- 루트 노드는 블랙이다.
- 모든 널 포인터는 블랙이다.
- 레드 노드의 자식노드 양쪽은 언제나 모두 블랙이다. (즉, 레드 노드는 연달아 나타날 수 없으며, 블랙 노드만이 레드 노드의 부모 노드가 될 수 있다)
- 어떤 노드로부터 시작되어 리프 노드에 도달하는 모든 경로에는 리프 노드를 제외하면 모두 같은 개수의 블랙 노드가 있다.
삽입
이진 탐색 트리와 같이 삽입 연산을 진행하고, 색을 빨간색으로 지정한다.
이후 레드 블랙 트리의 특징을 유지하기 위해서 회전 연산을 해야하는 경우들이 있다.
예를 들어서 부모 노드 P가 빨간색이고, 삼촌 노드 S는 검정색이다. 새로운 노드(N)는 PN의 오른쪽 자식 노드이며, P는 할아버지 노드 P^2의 왼쪽 자식 노드이다. 이 경우, N과 P의 역할을 변경하기 위해서 왼쪽 회전을 해줘야한다.
삭제
레드블랙트리의 노드를 삭제할 때 빨간색, 검정색 중 하나의 노드를 삭제하게 될 것이다.
빨간색 노드를 삭제하게 되는 경우 레드블랙트리의 특징을 건드리는 부분이 없으므로 고려해야 할 부분이 없다.
하지만 검정색 노드는 이와 달리 고려해볼 경우의 수들이 있다.
- 삭제된 노드를 대체하는 노드가 빨간색일 경우, 색상을 검정색으로 변경한다.
- 삭제된 노드를 대체하는 노드가 검정색이며, 형제가 빨간색인 경우 형제는 검정, 부모는 빨강으로 바꾸고 좌회전한다.
- ...
친절하게 설명을 잘 해주신 블로거님이 계셔서 자세한 내용들은 링크로 대체한다.
import java.util.Scanner;
public class RedBlackTree {
private final int RED = 0;
private final int BLACK = 1;
private class Node {
int key = -1, color = BLACK;
Node left = nil, right = nil, parent = nil;
Node(int key) {
this.key = key;
}
}
private final Node nil = new Node(-1);
private Node root = nil;
public void printTree(Node node) {
if (node == nil) {
return;
}
printTree(node.left);
System.out.print(((node.color == RED) ? "Color: Red " : "Color: Black ") + "Key: " + node.key + " Parent: " + node.parent.key + "\n");
printTree(node.right);
}
private Node findNode(Node findNode, Node node) {
if (root == nil) {
return null;
}
if (findNode.key < node.key) {
if (node.left != nil) {
return findNode(findNode, node.left);
}
} else if (findNode.key > node.key) {
if (node.right != nil) {
return findNode(findNode, node.right);
}
} else if (findNode.key == node.key) {
return node;
}
return null;
}
private void insert(Node node) {
Node temp = root;
if (root == nil) {
root = node;
node.color = BLACK;
node.parent = nil;
} else {
node.color = RED;
while (true) {
if (node.key < temp.key) {
if (temp.left == nil) {
temp.left = node;
node.parent = temp;
break;
} else {
temp = temp.left;
}
} else if (node.key >= temp.key) {
if (temp.right == nil) {
temp.right = node;
node.parent = temp;
break;
} else {
temp = temp.right;
}
}
}
fixTree(node);
}
}
private void fixTree(Node node) {
while (node.parent.color == RED) {
Node uncle = nil;
if (node.parent == node.parent.parent.left) {
uncle = node.parent.parent.right;
if (uncle != nil && uncle.color == RED) {
node.parent.color = BLACK;
uncle.color = BLACK;
node.parent.parent.color = RED;
node = node.parent.parent;
continue;
}
if (node == node.parent.right) {
node = node.parent;
rotateLeft(node);
}
node.parent.color = BLACK;
node.parent.parent.color = RED;
rotateRight(node.parent.parent);
} else {
uncle = node.parent.parent.left;
if (uncle != nil && uncle.color == RED) {
node.parent.color = BLACK;
uncle.color = BLACK;
node.parent.parent.color = RED;
node = node.parent.parent;
continue;
}
if (node == node.parent.left) {
node = node.parent;
rotateRight(node);
}
node.parent.color = BLACK;
node.parent.parent.color = RED;
rotateLeft(node.parent.parent);
}
}
root.color = BLACK;
}
void rotateLeft(Node node) {
if (node.parent != nil) {
if (node == node.parent.left) {
node.parent.left = node.right;
} else {
node.parent.right = node.right;
}
node.right.parent = node.parent;
node.parent = node.right;
if (node.right.left != nil) {
node.right.left.parent = node;
}
node.right = node.right.left;
node.parent.left = node;
} else {
Node right = root.right;
root.right = right.left;
right.left.parent = root;
root.parent = right;
right.left = root;
right.parent = nil;
root = right;
}
}
void rotateRight(Node node) {
if (node.parent != nil) {
if (node == node.parent.left) {
node.parent.left = node.left;
} else {
node.parent.right = node.left;
}
node.left.parent = node.parent;
node.parent = node.left;
if (node.left.right != nil) {
node.left.right.parent = node;
}
node.left = node.left.right;
node.parent.right = node;
} else {
Node left = root.left;
root.left = root.left.right;
left.right.parent = root;
root.parent = left;
left.right = root;
left.parent = nil;
root = left;
}
}
void transplant(Node target, Node with) {
if (target.parent == nil) {
root = with;
} else if (target == target.parent.left) {
target.parent.left = with;
} else
target.parent.right = with;
with.parent = target.parent;
}
boolean delete(Node z) {
if ((z = findNode(z, root)) == null) return false;
Node x;
Node y = z;
int y_original_color = y.color;
if (z.left == nil) {
x = z.right;
transplant(z, z.right);
} else if (z.right == nil) {
x = z.left;
transplant(z, z.left);
} else {
y = treeMinimum(z.right);
y_original_color = y.color;
x = y.right;
if (y.parent == z)
x.parent = y;
else {
transplant(y, y.right);
y.right = z.right;
y.right.parent = y;
}
transplant(z, y);
y.left = z.left;
y.left.parent = y;
y.color = z.color;
}
if (y_original_color == BLACK)
deleteFixup(x);
return true;
}
void deleteFixup(Node x) {
while (x != root && x.color == BLACK) {
if (x == x.parent.left) {
Node w = x.parent.right;
if (w.color == RED) {
w.color = BLACK;
x.parent.color = RED;
rotateLeft(x.parent);
w = x.parent.right;
}
if (w.left.color == BLACK && w.right.color == BLACK) {
w.color = RED;
x = x.parent;
continue;
} else if (w.right.color == BLACK) {
w.left.color = BLACK;
w.color = RED;
rotateRight(w);
w = x.parent.right;
}
if (w.right.color == RED) {
w.color = x.parent.color;
x.parent.color = BLACK;
w.right.color = BLACK;
rotateLeft(x.parent);
x = root;
}
} else {
Node w = x.parent.left;
if (w.color == RED) {
w.color = BLACK;
x.parent.color = RED;
rotateRight(x.parent);
w = x.parent.left;
}
if (w.right.color == BLACK && w.left.color == BLACK) {
w.color = RED;
x = x.parent;
continue;
} else if (w.left.color == BLACK) {
w.right.color = BLACK;
w.color = RED;
rotateLeft(w);
w = x.parent.left;
}
if (w.left.color == RED) {
w.color = x.parent.color;
x.parent.color = BLACK;
w.left.color = BLACK;
rotateRight(x.parent);
x = root;
}
}
}
x.color = BLACK;
}
Node treeMinimum(Node subTreeRoot) {
while (subTreeRoot.left != nil) {
subTreeRoot = subTreeRoot.left;
}
return subTreeRoot;
}
public void consoleUI() {
Scanner scan = new Scanner(System.in);
while (true) {
System.out.println("\n1.- Add items\n"
+ "2.- Delete items\n"
+ "3.- Search items\n");
int choice = scan.nextInt();
int item;
Node node;
switch (choice) {
case 1:
item = scan.nextInt();
while (item != -10) {
node = new Node(item);
insert(node);
item = scan.nextInt();
}
printTree(root);
break;
case 2:
item = scan.nextInt();
while (item != -10) {
node = new Node(item);
System.out.print("\nDeleting item " + item);
if (delete(node)) {
System.out.print(": deleted!");
} else {
System.out.print(": does not exist!");
}
item = scan.nextInt();
}
System.out.println();
printTree(root);
break;
case 3:
item = scan.nextInt();
while (item != -10) {
node = new Node(item);
System.out.println((findNode(node, root) != null) ? "found" : "not found");
item = scan.nextInt();
}
break;
}
}
}
public static void main(String[] args) {
RedBlackTree rbt = new RedBlackTree();
rbt.consoleUI();
}
}
이전의 치킨 클래스, 노드 클래스랑 같이 사용하려고 하면 왠지 모르게 치킨 클래스를 참조하지 못하는 오류가 발생해서 단일 키값으로만 구현했다. 정확히는 여러 사람들이 올린 소스코드를 보고 이해가능한 부분들만 쏙쏙 뽑아서 사용했다..
참고 사이트
https://ko.wikipedia.org/wiki/%EB%A0%88%EB%93%9C-%EB%B8%94%EB%9E%99_%ED%8A%B8%EB%A6%AC
https://github.com/Arsenalist/Red-Black-Tree-Java-Implementation/blob/master/src/RedBlackTree.java
https://www.geeksforgeeks.org/left-leaning-red-black-tree-insertion/
'알고리즘 > 이론' 카테고리의 다른 글
KDTree (0) | 2019.04.30 |
---|---|
B Tree (0) | 2019.04.30 |
이진 탐색 트리(BST) 알고리즘 (0) | 2019.04.02 |
특수 정렬 알고리즘(기수 정렬) (0) | 2019.03.29 |
힙 정렬 (0) | 2019.03.29 |