[참고 사이트]
https://velog.io/@wakeupmakeup/AVL-%ED%8A%B8%EB%A6%AC
https://velog.io/@dankj1991/Tree-AVL-Tree
https://yoongrammer.tistory.com/72
https://www.youtube.com/watch?v=9BiHgy40NNE&ab_channel=%EC%BD%94%EB%93%9C%EB%9D%BC%EB%96%BC
AVL 트리(Adelson-Velsky and Landis Tree)는 스스로 균형을 잡는 이진 탐색 트리입니다.
균형 이진 탐색 트리(Self-balancing Binary Search Tree)의 일종으로, 트리의 높이를 항상 일정하게 유지하여 탐색, 삽입, 삭제 연산에서 O(log n)의 성능을 보장하는 자료구조입니다.

위와 같이 일반적인 BST는 한쪽 노드가 치우치는 상황이 생길 수 있습니다.
이런 상황에서 특정 값을 찾으면 전체 경우의 수를 찾아야하는 O(N)의 시간이 걸릴 수 있습니다.
그러므로 트리의 높이가 높아지는 단점을 방지하고자 높이 균형을 유지하는 AVL 트리를 사용합니다.
→ 높이가 2 이상이면 균형을 다시 잡습니다.
을 유지해야합니다.

[장점]
[단점]
앞서 말한것처럼 Balance Factor(BF)는 외쪽 서브트리의 높이에서 오른쪽 서브트리의 높이를 뺀 값입니다.
→ Balance Factor (k) = height (left(k)) - height(right(k))
AVL 트리는 삽입(Insertion), 삭제(Deletion) 시 트리의 균형을 유지하기 위해 회전 연산을 사용합니다. 앞으로 트리의 균형이 깨졌을 때 회전으로 균형을 되찾는 과정을 설명합니다.
삽입 삭제시 노드들의 배열에 따라 4가지(LL, RR, LR, RL) 불균형이 발생할 수 있으며 각 상황마다 rotation에 방향을 달리하여 트리의 균형을 맞춥니다.
[균형이 깨지는 4가지 Case]
→ 각 케이스에 맞는 회전 연산을 적용하여 트리의 균형을 맞출 수 있습니다.
트리가 불균형 상태에 있을 때, 이를 해결하기 위해 단일 회전(Single Rotation) 또는 이중 회전(Double Rotation)이 사용됩니다.
다음과 같이 각 회전 케이스를 표로 정리할 수 있습니다:
| 회전 종류 | 조건(루트 노드의 BF) | 자식 노드의 BF 조건 | 설명 |
|---|---|---|---|
| LL 케이스 | BF ≥ +2 | 왼쪽 자식의 BF ≥ 0 | 왼쪽 자식이 다시 왼쪽으로 치우친 경우.단순 LL 회전으로 해결. |
| RR 케이스 | BF ≤ -2 | 오른쪽 자식의 BF ≤ 0 | 오른쪽 자식이 다시 오른쪽으로 치우친 경우.단순 RR 회전으로 해결. |
| LR 케이스 | BF ≥ +2 | 왼쪽 자식의 BF < 0 | 왼쪽 자식이 오른쪽으로 치우친 경우.먼저 RR 회전 후, LL 회전. |
| RL 케이스 | BF ≤ -2 | 오른쪽 자식의 BF > 0 | 오른쪽 자식이 왼쪽으로 치우친 경우.먼저 LL 회전 후, RR 회전. |

LL에서는 Right Ratation을 수행합니다.
[C언어 코드 구현]
struct node *rightRotate (struct node *z) {
struct node *y = z->left;
struct node *T2 = y->right;
// right 회전 수행
y->right = z;
z->left = T2;
// 노드 높이 갱신
z->height = 1 + max(z->left->height, z->right->height);
y->height = 1 + max(y->left->height, y->right->height);
// 새로운 루트 노드 y를 반환
return y;
}

RR에서는 Left Ratation을 수행합니다.
[C언어 코드 구현]
struct node *leftRotate (struct node *z) {
struct node *y = z->right;
struct node *T2 = y->left;
// left 회전 수행
y->left = z;
z->right = T2;
// 노드 높이 갱신
z->height = 1 + max(z->left->height, z->right->height);
y->height = 1 + max(y->left->height, y->right->height);
// 새로운 루트 노드 y를 반환
return y;
}

LR에서는 RR케이스를 먼저 수행하고 LL케이스를 적용합니다.
[C언어 코드 구현]
y = z->left;
y = leftRotate(y);
z = rightRotate(z);

RL에서는 LL케이스를 먼저 수행하고 RR케이스를 적용합니다.
LR의 케이스와 비슷하게 총 두번의 Rotation을 통해 균형을 맞춥니다. 직접해보면서 감을 찾으면 좋을 것 같습니다.
[C언어 코드 구현]
y = z->right;
y = rightRotate(y);
z = leftRotate(z);
#include <stdio.h>
#include <stdlib.h>
// 노드 구조체 정의
typedef struct Node {
int key;
int height;
struct Node* left;
struct Node* right;
} Node;
// 높이 구하는 함수
int height(Node* N) {
if (N == NULL)
return 0;
return N->height;
}
// 최대값 반환
int max(int a, int b) {
return (a > b) ? a : b;
}
// 새 노드 생성 함수
Node* newNode(int key) {
Node* node = (Node*)malloc(sizeof(Node));
node->key = key;
node->left = node->right = NULL;
node->height = 1; // 새 노드는 높이 1
return node;
}
// 오른쪽 회전
Node* rightRotate(Node* y) {
Node* x = y->left;
Node* T2 = x->right;
// 회전 수행
x->right = y;
y->left = T2;
// 높이 갱신
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
return x; // 새로운 루트
}
// 왼쪽 회전
Node* leftRotate(Node* x) {
Node* y = x->right;
Node* T2 = y->left;
// 회전 수행
y->left = x;
x->right = T2;
// 높이 갱신
x->height = max(height(x->left), height(x->right)) + 1;
y->height = max(height(y->left), height(y->right)) + 1;
return y; // 새로운 루트
}
// 균형 계수 구하기
int getBalance(Node* N) {
if (N == NULL)
return 0;
return height(N->left) - height(N->right);
}
// AVL 트리 삽입 함수
Node* insert(Node* node, int key) {
// 1. 이진 탐색 트리 삽입
if (node == NULL)
return newNode(key);
if (key < node->key)
node->left = insert(node->left, key);
else if (key > node->key)
node->right = insert(node->right, key);
else // 중복 키는 허용하지 않음
return node;
// 2. 높이 갱신
node->height = 1 + max(height(node->left), height(node->right));
// 3. 균형 계수 계산
int balance = getBalance(node);
// 4. 균형을 맞추기 위한 회전
// LL (왼쪽 왼쪽)
if (balance > 1 && key < node->left->key)
return rightRotate(node);
// RR (오른쪽 오른쪽)
if (balance < -1 && key > node->right->key)
return leftRotate(node);
// LR (왼쪽 오른쪽)
if (balance > 1 && key > node->left->key) {
node->left = leftRotate(node->left);
return rightRotate(node);
}
// RL (오른쪽 왼쪽)
if (balance < -1 && key < node->right->key) {
node->right = rightRotate(node->right);
return leftRotate(node);
}
return node;
}
// 중위 순회 (정렬된 출력)
void inorder(Node* root) {
if (root != NULL) {
inorder(root->left);
printf("%d ", root->key);
inorder(root->right);
}
}
// 메인 함수
int main() {
Node* root = NULL;
// 예제 입력
int keys[] = {10, 20, 30, 40, 50, 25};
int n = sizeof(keys) / sizeof(keys[0]);
for (int i = 0; i < n; i++)
root = insert(root, keys[i]);
printf("중위 순회 결과 (정렬된 키): ");
inorder(root);
printf("\n");
return 0;
}
해당 과정을 더 상세히 보고 싶으면 아래의 벨로그를 들어가서 확인해 봅시다.(삽입, 삭제 등)
https://velog.io/@dankj1991/Tree-AVL-Tree