AVL 트리와 레드-블랙 트리는 모두 이진 탐색 트리의 일종으로, 데이터를 정렬된 순서로 유지하며 효율적인 탐색, 삽입 및 삭제 연산을 제공합니다. 이들 트리는 균형을 유지하기 위해 추가적인 제약을 도입해 높이가 최소한으로 유지되도록 합니다. 이로 인해 이진 탐색 트리에서 최악의 경우 발생할 수 있는 성능 저하를 방지합니다.
레드-블랙 트리에서도 삽입과 삭제 시 트리의 균형을 유지하기 위해 회전 및 색상 조정을 수행합니다. 이 균형 조건 덕분에 레드-블랙 트리의 높이도 항상 O(log N)을 유지하게 됩니다.
AVL 트리와 레드-블랙 트리의 주요 차이점은 균형 유지 방식과 연관되어 있습니다. 두 트리 모두 균형을 유지하기 위해 회전 연산을 수행하지만, AVL 트리는 완전히 균형된 트리를 목표로 하여 노드 간 높이 차이를 최대 1로 제한하는 반면, 레드-블랙 트리는 트리 균형을 색상 조정을 통해 유지하며 완전한 균형을 추구하지 않습니다.
이러한 차이점으로 인해 AVL 트리와 레드-블랙 트리는 다음과 같은 특성을 보입니다:
결론적으로, AVL 트리와 레드-블랙 트리는 서로 다른 균형인 유지 방식을 가지고 있으며, 이에 따라 특성과 성능이 달라집니다. 특정 상황에 따라 두 트리 중 하나가 더 적합한 선택이 될 수 있습니다.
AVL 트리
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
this.height = 1; // 높이 정보를 저장
}
}
class AVLTree {
constructor() {
this.root = null;
}
getHeight(node) {
if (node === null) {
return 0;
}
return node.height;
}
updateHeight(node) {
node.height = Math.max(this.getHeight(node.left), this.getHeight(node.right)) + 1;
}
getBalanceFactor(node) {
return this.getHeight(node.left) - this.getHeight(node.right);
}
// 오른쪽으로 회전
rotateRight(node) {
const newRoot = node.left;
node.left = newRoot.right;
newRoot.right = node;
this.updateHeight(node);
this.updateHeight(newRoot);
return newRoot;
}
// 왼쪽으로 회전
rotateLeft(node) {
const newRoot = node.right;
node.right = newRoot.left;
newRoot.left = node;
this.updateHeight(node);
this.updateHeight(newRoot);
return newRoot;
}
insert(value) {
this.root = this.insertNode(this.root, value);
}
insertNode(node, value) {
// 1. 기본 BST 삽입
if (node === null) {
return new TreeNode(value);
}
if (value < node.value) {
node.left = this.insertNode(node.left, value);
} else if (value > node.value) {
node.right = this.insertNode(node.right, value);
} else {
// 같은 값은 삽입하지 않음
return node;
}
// 2. 높이 갱신
this.updateHeight(node);
// 3. 균형인자 계산 및 회전
const balanceFactor = this.getBalanceFactor(node);
// 왼쪽 자식이 높아서 불균형 발생
if (balanceFactor > 1) {
// 오른쪽 회전 전, 왼쪽 자식의 오른쪽 자식이 더 높다면 왼쪽 자식을 왼쪽으로 회전
if (value > node.left.value) {
node.left = this.rotateLeft(node.left);
}
return this.rotateRight(node);
}
// 오른쪽 자식이 높아서 불균형 발생
if (balanceFactor < -1) {
// 왼쪽 회전 전, 오른쪽 자식의 왼쪽 자식이 더 높다면 오른쪽 자식을 오른쪽으로 회전
if (value < node.right.value) {
node.right = this.rotateRight(node.right);
}
return this.rotateLeft(node);
}
return node;
}
}
// 테스트 코드
const avl = new AVLTree();
avl.insert(10);
avl.insert(20);
avl.insert(30);
avl.insert(40);
avl.insert(50);
console.log(avl.root.value); // 출력: 30
Red-Black 트리
class TreeNode {
constructor(value, isRed = true) {
this.value = value;
this.left = null;
this.right = null;
this.isRed = isRed; // 노드의 색상을 저장
}
}
class RedBlackTree {
constructor() {
this.root = null;
}
isRed(node) {
if (node === null) {
return false; // null 노드는 항상 black입니다.
}
return node.isRed;
}
// 왼쪽 회전
rotateLeft(node) {
const newRoot = node.right;
node.right = newRoot.left;
newRoot.left = node;
newRoot.isRed = node.isRed;
node.isRed = true;
return newRoot;
}
// 오른쪽 회전
rotateRight(node) {
const newRoot = node.left;
node.left = newRoot.right;
newRoot.right = node;
newRoot.isRed = node.isRed;
node.isRed = true;
return newRoot;
}
flipColors(node) {
node.isRed = !node.isRed;
node.left.isRed = !node.left.isRed;
node.right.isRed = !node.right.isRed;
}
insert(value) {
this.root = this.insertNode(this.root, value);
this.root.isRed = false;
}
insertNode(node, value) {
if (node === null) {
return new TreeNode(value);
}
if (value < node.value) {
node.left = this.insertNode(node.left, value);
} else if (value > node.value) {
node.right = this.insertNode(node.right, value);
} else {
// 같은 값은 삽입하지 않음
return node;
}
// 회전 및 색상 변경
if (this.isRed(node.right) && !this.isRed(node.left)) {
node = this.rotateLeft(node);
}
if (this.isRed(node.left) && this.isRed(node.left.left)) {
node = this.rotateRight(node);
}
if (this.isRed(node.left) && this.isRed(node.right)) {
this.flipColors(node);
}
return node;
}
}
// 테스트 코드
const rbt = new RedBlackTree();
rbt.insert(10);
rbt.insert(20);
rbt.insert(30);
rbt.insert(40);
rbt.insert(50);
console.log(rbt.root.value); // 출력: 20