개념공부
트리의 회전과 AVL 트리
Cadi
2024. 12. 11. 00:59
AVL 트리
- 왼쪽 서브트리의 높이와 오른쪽 서브트리의 높이가 1 이상 차이나지 않는 높이 이진 균형 트리
- 검색 속도 O(log n), 삽입/삭제 연산 속도 O(log n)\
- 왜 필요한가 ? : 완전 편향된 트리가 존재할 수 있기 때문에 조정 해주어야 함
AVL 트리를 구현하기 위해서는 우선 회전에 대해 알아야 한다.
회전
- 트리가 한쪽으로 치우친 불균형 상태를 해결하기 위해 노드의 연결 관계를 재배치하는 작업
- 트리의 높이를 조정하여, 탐색 시간의 시간 복잡도를 유지함
- 특성( 왼쪽 자식 < 부모 < 오른쪽 자식)을 유지함
회전의 종류
- LL 회전 ( Left - Left Rotation) : 왼쪽 서브트리의 왼쪽 자식에서 불균형 발생시 오른쪽으로 회전
- RR 회전 (Right- Right) : 오른쪽 서브트리의 오른쪽 자식에서 불균형 발생시 왼쪽으로 회전
- LR 회전(Left - Right) : 왼쪽 서브트리의 오른쪽 자식에서 불균형 발생시 왼쪽으로 회전 후 오른쪽 회전
- RL 회전 (Right -Left) : 오른쪽 서브트리의 왼쪽 자식에서 불균형 발생시 오른쪽으로 회전 후 왼쪽 회전
예시
- LL 회전
과정 : 부모(10)을 기준으로 오른쪽으로 회전함
새로운 루트 5를 설정
원래 루트(10)은 5의 오른쪽 자식이 됨
2. RR 회전
과정 : 부모(10)을 기준으로 왼쪽으로 회전함
새로운 루트 20을 설정
원래 루트(10)은 20의 왼쪽 자식이 됨
3.LR 회전
과정 : 왼쪽 자식(5)를 기준으로 왼쪽 회전
부모(10)을 기준으로 오른쪽 회전
4. RL 회전
과정 : 오른쪽 자식(20)을 기준으로 오른쪽 회전
부모(10)을 기준으로 왼쪽 회전
// AVL 트리 클래스
public class AVLTree<T> where T : IComparable<T>
{
private AVLNode<T> root; // 트리의 루트 노드
// 1. 노드의 높이를 가져오는 헬퍼 함수
private int GetHeight(AVLNode<T> node)
{
return node == null ? 0 : node.Height;
}
// 2. 노드의 균형 팩터를 계산하는 함수
private int GetBalance(AVLNode<T> node)
{
return node == null ? 0 : GetHeight(node.Left) - GetHeight(node.Right);
}
// 3. 오른쪽으로 회전 (Left Rotation)
private AVLNode<T> RotateRight(AVLNode<T> y)
{
AVLNode<T> x = y.Left;
AVLNode<T> T2 = x.Right;
// 회전
x.Right = y;
y.Left = T2;
// 높이 업데이트
y.Height = Math.Max(GetHeight(y.Left), GetHeight(y.Right)) + 1;
x.Height = Math.Max(GetHeight(x.Left), GetHeight(x.Right)) + 1;
return x; // 새로운 루트 반환
}
// 4. 왼쪽으로 회전 (Right Rotation)
private AVLNode<T> RotateLeft(AVLNode<T> x)
{
AVLNode<T> y = x.Right;
AVLNode<T> T2 = y.Left;
// 회전
y.Left = x;
x.Right = T2;
// 높이 업데이트
x.Height = Math.Max(GetHeight(x.Left), GetHeight(x.Right)) + 1;
y.Height = Math.Max(GetHeight(y.Left), GetHeight(y.Right)) + 1;
return y; // 새로운 루트 반환
}
// 5. 노드 삽입
public void Insert(T key)
{
root = InsertNode(root, key); // 루트 노드에서 시작
}
private AVLNode<T> InsertNode(AVLNode<T> node, T key)
{
// 기본 BST 삽입
if (node == null)
{
return new AVLNode<T>(key); // 새 노드 생성
}
if (key.CompareTo(node.Key) < 0)
{
node.Left = InsertNode(node.Left, key);
}
else if (key.CompareTo(node.Key) > 0)
{
node.Right = InsertNode(node.Right, key);
}
else
{
return node; // 중복 키는 삽입하지 않음
}
// 높이 업데이트
node.Height = Math.Max(GetHeight(node.Left), GetHeight(node.Right)) + 1;
// 균형 확인 및 조정
int balance = GetBalance(node);
// LL(Left-Left) 불균형: 오른쪽으로 회전
if (balance > 1 && key.CompareTo(node.Left.Key) < 0)
{
return RotateRight(node);
}
// RR(Right-Right) 불균형: 왼쪽으로 회전
if (balance < -1 && key.CompareTo(node.Right.Key) > 0)
{
return RotateLeft(node);
}
// LR(Left-Right) 불균형: 왼쪽 회전 후 오른쪽 회전
if (balance > 1 && key.CompareTo(node.Left.Key) > 0)
{
node.Left = RotateLeft(node.Left);
return RotateRight(node);
}
// RL(Right-Left) 불균형: 오른쪽 회전 후 왼쪽 회전
if (balance < -1 && key.CompareTo(node.Right.Key) < 0)
{
node.Right = RotateRight(node.Right);
return RotateLeft(node);
}
return node; // 균형이 맞아졌으므로 현재 노드 반환
}
// 6. 트리 출력 (디버깅용)
public void PrintTree()
{
PrintTree(root, "", true);
}
private void PrintTree(AVLNode<T> node, string indent, bool last)
{
if (node != null)
{
Console.WriteLine(indent + (last ? "└─ " : "├─ ") + node.Key);
indent += last ? " " : "│ ";
PrintTree(node.Left, indent, false);
PrintTree(node.Right, indent, true);
}
}
}
전체적인 과정은, 새로운 노드를 추가하면 높이를 검사해서 그것이 어떤 상태인지(LL,RR,LR,RL) 검사하고
그에 맞는 회전을 주어 올바른 트리를 유지하는 것이다. 회전만 이해하면 AVL 트리 자체는 어렵지 않으니
자세한 설명은 넘어간다.