ABOUT

성능과 운영 안정성을 함께 끌어올리는 개발자입니다.

92% Positional Error Reduction
79% p95 Latency Improvement
90%+ Long Tasks Reduction

2022.02 · 한국장학재단

우수 멘티

한국장학재단 사회 리더 대학생 멘토링 IT

2022.10 · 동작구청

우수 인재상

동작구청 우수 SW 인재

2025.05 · (주) 그랩

프로그래밍 우수상

(주) 그랩 우수 프로그램 개발

2025.05 · AWSKRUG

AWS한국사용자모임 발표

AI agent 스크립트 튜닝 관련 발표

ComputerScience

Development

Engineering

Trouble Shooting

GUESTBOOK

첫 마음부터
함께 나누는 온기

방명록 작성하러 가기

SUBSCRIBE

최신소식을
편하게 만나보세요.

AVL 트리

도입

AVL 트리는 BST의 검색 규칙은 그대로 유지하면서도, 높이가 한쪽으로 무너지는 문제를 회전(rotation)으로 제어하는 균형 이진 검색 트리다.

BST는 왼쪽은 더 작고 오른쪽은 더 크다는 규칙 덕분에 검색이 빠르지만, 입력 순서가 나쁘면 한쪽으로 길게 치우쳐 연결 리스트처럼 변할 수 있습니다.

AVL 트리는 이 문제를 해결하기 위해 각 노드에서 왼쪽과 오른쪽 서브트리의 높이 차이를 제한합니다. 즉 BST property 위에 “균형 조건”을 하나 더 올려놓은 구조라고 보면 이해가 쉽습니다.

그래서 AVL 트리를 이해한다는 것은 단순히 검색 트리를 하나 더 배우는 것이 아니라, BST의 탐색 성질을 유지하면서 최악의 높이를 어떻게 제어할 것인지 이해하는 일에 가깝습니다.

필요성

정렬 기반 검색이 필요하면서도 최악의 경우에 탐색 성능이 무너지는 것은 허용할 수 없을 때, AVL 트리는 BST보다 훨씬 안정적인 선택이 된다

일반 BST는 평균적으로는 잘 동작해도, 삽입 순서가 오름차순이나 내림차순처럼 치우치면 높이가 급격히 커질 수 있습니다. 이 경우 검색, 삽입, 삭제의 핵심 연산이 모두 길어진 경로를 따라가야 하므로 성능이 나빠집니다.

AVL 트리는 이런 편향을 구조적으로 막아 줍니다. 각 노드에서 두 서브트리의 높이 차이를 제한하므로, 트리 높이가 로그 범위 안에 머물고, 결과적으로 검색과 업데이트 성능도 더 예측 가능해집니다.

즉 AVL의 가치는 단순히 “균형이 맞다”는 데 있지 않고, BST가 가진 좋은 검색 성질을 최악의 경우에도 유지하려는 데 있습니다.

AVL 트리가 특히 강한 지점
  • 정렬 기반 검색을 자주 해야 할 때
  • 최악의 경우에도 로그 높이를 유지하고 싶을 때
  • 삽입과 삭제가 섞여 들어오지만 성능 편차를 줄이고 싶을 때
  • 중위 순회를 통해 정렬 순 접근도 함께 얻고 싶을 때

정의

AVL 트리는 각 노드의 두 서브트리 높이 차이가 최대 1인, 즉 height-balanced 조건을 만족하는 이진 검색 트리다

AVL 트리는 기본적으로 BST입니다. 따라서 왼쪽 서브트리의 키는 현재 노드보다 작고, 오른쪽 서브트리의 키는 더 커야 합니다.

여기에 AVL balancing rule이 추가됩니다. 각 노드에서 왼쪽과 오른쪽 서브트리의 높이 차이가 -1, 0, +1 범위를 벗어나면 그 트리는 AVL 트리가 아닙니다.

즉 AVL은 “검색 규칙”과 “균형 규칙”을 동시에 만족해야 하는 자료구조입니다. 둘 중 하나라도 깨지면 단순 BST 또는 단순 이진 트리일 뿐, AVL이라고 부를 수 없습니다.

핵심 메시지

"AVL 트리의 본질은 BST 위에

높이 차이를 제한하는 균형 규칙을 더해 최악의 높이를 억제하는 데 있습니다."

핵심 원리

AVL의 핵심은 삽입과 삭제 뒤에 높이 차이가 깨진 노드를 찾아, BST의 중위 순서를 보존하는 회전으로 다시 균형을 회복시키는 데 있다

AVL 트리의 검색은 BST와 같습니다. 루트에서 시작해 더 작으면 왼쪽, 더 크면 오른쪽으로 내려갑니다.

차이는 수정 연산 이후에 드러납니다. 삽입이나 삭제 때문에 어떤 노드의 두 서브트리 높이 차이가 2 이상이 되면 AVL invariant가 깨집니다. 이때 회전을 적용해 구조를 다시 바꿉니다.

회전은 트리를 다시 정렬하는 과정이 아니라, 노드들을 위아래로 재배치해 높이를 조정하는 지역적(local) 연산입니다. 중요한 점은 회전 후에도 in-order 순서는 그대로 보존된다는 것입니다.

즉 AVL은 “정렬을 유지하면서 높이를 줄이는” 구조이고, 이 두 조건을 동시에 만족시키는 핵심 도구가 rotation입니다.

AVL Operation Model
1) BST 규칙으로 탐색한다
2) 삽입 또는 삭제를 수행한다
3) 조상 방향으로 올라가며 높이 차이를 확인한다
4) 불균형이 생긴 지점에서 rotation 으로 재균형한다
5) BST property 와 AVL balancing rule 을 함께 복구한다

핵심 불변식

AVL 트리를 제대로 이해하려면 BST property 와 balancing rule 을 अलग개념이 아니라 동시에 지켜야 하는 이중 불변식으로 봐야 한다
불변식 의미 실무 해석
BST Property 왼쪽 서브트리 키 < 현재 키 < 오른쪽 서브트리 키 검색과 중위 순 정렬 순회가 가능해짐
AVL Balancing Rule 각 노드의 두 서브트리 높이 차이 ≤ 1 트리 높이가 지나치게 커지는 것을 방지
Balance Factor 왼쪽/오른쪽 높이 차이를 요약하는 값 보통 -1, 0, +1 범위를 유지해야 안정적
Rotation Safety 회전 후에도 중위 순서가 보존됨 검색 구조를 깨뜨리지 않고 높이만 조정 가능

즉 AVL 디버깅은 “값 비교 규칙이 맞는가”와 “높이 차이 규칙이 맞는가”를 동시에 보는 일입니다. 둘 중 하나만 지켜도 충분하지 않습니다.

기본 구조

AVL 트리는 기본 BST 노드에 높이 또는 balance factor 정보를 하나 더 붙여, 수정 연산 뒤 불균형을 빠르게 감지할 수 있게 만든 구조다
avl-tree/
├── root
└── node
    ├── key
    ├── left
    ├── right
    └── height or balance-factor metadata

일반 BST와 가장 큰 차이는 노드가 균형 정보를 함께 관리한다는 점입니다. 구현에 따라 높이를 저장하기도 하고, balance factor만 저장하기도 합니다.

이 메타데이터 덕분에 삽입·삭제 후 어느 지점이 무너졌는지 빠르게 확인할 수 있고, 필요한 회전도 지역적으로 적용할 수 있습니다.

기본 구현

AVL 구현의 중심은 검색이 아니라 회전과 높이 갱신이며, 삽입 코드는 결국 BST 삽입 뒤 재균형 단계가 붙는 형태가 된다
class Node {
    int key;
    int height;
    Node left;
    Node right;

    Node(int key) {
        this.key = key;
        this.height = 1;
    }
}

int height(Node n) {
    return n == null ? 0 : n.height;
}

int balance(Node n) {
    return n == null ? 0 : height(n.left) - height(n.right);
}

void updateHeight(Node n) {
    n.height = 1 + Math.max(height(n.left), height(n.right));
}

Node rotateRight(Node y) {
    Node x = y.left;
    Node t2 = x.right;

    x.right = y;
    y.left = t2;

    updateHeight(y);
    updateHeight(x);
    return x;
}

Node rotateLeft(Node x) {
    Node y = x.right;
    Node t2 = y.left;

    y.left = x;
    x.right = t2;

    updateHeight(x);
    updateHeight(y);
    return y;
}

Node insert(Node node, int key) {
    if (node == null) return new Node(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;
    }

    updateHeight(node);
    int bf = balance(node);

    if (bf > 1 && key < node.left.key) return rotateRight(node);          // LL
    if (bf < -1 && key > node.right.key) return rotateLeft(node);         // RR
    if (bf > 1 && key > node.left.key) {                                  // LR
        node.left = rotateLeft(node.left);
        return rotateRight(node);
    }
    if (bf < -1 && key < node.right.key) {                                // RL
        node.right = rotateRight(node.right);
        return rotateLeft(node);
    }

    return node;
}
실전 포인트
AVL 코드는 검색보다 재균형 로직에서 복잡성이 올라갑니다. 실제로는 insert 자체보다도 회전 후 높이를 정확히 다시 계산하고, 서브트리 루트가 바뀐 사실을 부모 쪽에 정확히 돌려주는 부분이 핵심입니다.

패턴 1. 회전은 정렬을 깨지 않고 높이를 줄인다

AVL에서 회전은 노드를 좌우로 다시 정렬하는 작업이 아니라, 중위 순서를 보존한 채 위아래 위치를 바꿔 균형을 복구하는 국소 연산이다

NIST는 left rotation을 어떤 노드를 아래 왼쪽으로 밀어 내리고 그 오른쪽 자식이 위로 올라오는 연산으로, right rotation은 그 대칭 구조로 설명합니다.

중요한 점은 회전이 BST 순서를 깨뜨리지 않는다는 것입니다. 그래서 검색 규칙은 유지하면서도, 높이 차이가 큰 구간만 국소적으로 조정할 수 있습니다.

실무에서는 이 불균형을 보통 LL, RR, LR, RL 네 패턴으로 나누어 설명합니다. 하지만 본질은 이름보다도 단일 회전이 필요한지, 두 번의 회전이 필요한지에 있습니다.

Rotation Patterns
- single rotation
  - left rotation
  - right rotation

- double rotation
  - left-right
  - right-left

패턴 2. 삽입은 BST 삽입 + 재균형

AVL 삽입은 새로운 알고리즘이라기보다, BST 삽입 뒤 위로 올라가며 높이와 balance 를 갱신하고 필요한 회전을 적용하는 방식이다

삽입 자체는 BST와 같습니다. 새 키와 비교를 반복해 적절한 leaf 위치를 찾고, 그 자리에 노드를 붙입니다.

차이는 그 이후입니다. 삽입으로 인해 어떤 조상 노드의 높이 차이가 2 이상 벌어질 수 있으므로, 삽입 지점에서 루트 방향으로 올라가며 높이와 balance를 갱신하고 필요하면 회전합니다.

즉 AVL 삽입은 “검색 규칙”과 “균형 회복 규칙”이 결합된 연산으로 보는 편이 맞습니다.

패턴 3. 삭제는 더 조심스럽다

AVL 삭제는 BST 삭제로 끝나지 않고, 높이 감소가 위로 전파될 수 있기 때문에 조상 경로를 따라 재균형 여부를 계속 확인해야 하는 경우가 많다

삭제의 첫 단계는 여전히 BST 삭제입니다. leaf 제거, 자식 하나 splice, successor 치환 같은 기본 구조는 BST와 같습니다.

하지만 삭제는 높이를 줄일 수 있기 때문에, 불균형이 부모와 더 위 조상으로 연쇄적으로 퍼질 수 있습니다. 그래서 AVL 삭제는 삽입보다 구현 난이도가 더 높게 느껴지는 경우가 많습니다.

즉 삭제는 “노드를 뺀다”보다 “노드를 뺀 뒤에도 AVL invariant가 위로 계속 유지되는지 확인한다”로 이해하는 편이 더 정확합니다.

핵심 포인트
AVL를 배우는 이유는 단순히 회전 네 가지를 외우기 위해서가 아닙니다. 중요한 것은 BST의 검색 규칙과 균형 규칙이 어떻게 함께 유지되는지, 그리고 수정 연산 이후 그 규칙을 지역적인 회전만으로 복구할 수 있다는 점을 이해하는 데 있습니다.

한계와 주의점

AVL 트리는 검색 성능은 강하게 보장하지만, 그 대가로 삽입·삭제 구현과 회전 관리가 plain BST 보다 훨씬 복잡해진다

AVL의 장점은 높이를 강하게 제어한다는 점입니다. 하지만 그만큼 각 노드에 높이 또는 balance 정보를 유지해야 하고, 수정 연산마다 회전 여부를 판단해야 합니다.

즉 AVL은 “빠른 검색을 공짜로 얻는 구조”가 아니라, 더 복잡한 유지 비용을 지불하고 최악의 높이를 관리하는 구조입니다.

그래서 실무에서는 직접 AVL을 구현하기보다, 언어 또는 라이브러리가 제공하는 검증된 balanced tree 계열 구조를 사용하는 경우가 많습니다.

주의해야 할 지점
  • BST property 와 AVL balancing rule 을 동시에 지켜야 함
  • 회전 후 높이 갱신 순서를 잘못 잡으면 구조가 쉽게 틀어짐
  • 삭제는 삽입보다 위쪽 조상까지 영향이 퍼질 가능성이 큼
  • 직접 구현 시 parent/root 링크 갱신 실수가 매우 흔함

자주 하는 실수

AVL을 어렵게 만드는 가장 흔한 원인은 회전을 외워도, 왜 그 회전이 BST 순서를 보존하는지와 왜 높이 갱신이 필요한지를 따로 이해하지 않는 데 있다
  • AVL이면 아무 회전이나 해도 된다고 생각함
  • BST property 만 맞으면 AVL도 자동으로 맞는다고 착각함
  • 회전 후 height 또는 balance factor 재계산을 빼먹음
  • 삭제를 단순 BST 삭제로 끝내고 위쪽 재균형을 놓침
  • 중위 순회 결과 검증 없이 balance 값만 보고 맞다고 판단함
  • 루트가 회전으로 바뀌는 케이스를 놓침

실무 루틴

AVL를 적용할 때는 먼저 ordered query와 최악의 검색 높이가 정말 중요한지 판단하고, 직접 구현한다면 회전보다 검증 로직부터 함께 준비하는 편이 안전하다
  1. 먼저 plain BST 로 충분한지, 아니면 worst-case 로그 높이 가 필요한지 판단한다.
  2. 검색뿐 아니라 predecessor, successor, ordered traversal 같은 정렬 질의 가 중요한지 본다.
  3. 직접 구현한다면 inorder 검증, height 검증, balance 검증 테스트를 먼저 만든다.
  4. 회전 함수는 단독으로 검증하고, insert/delete 와 분리해서 테스트한다.
  5. 삭제 케이스는 leaf, one child, two children, root replacement 를 분리해 점검한다.
  6. 실서비스에서는 직접 구현보다 검증된 라이브러리 구조를 우선 검토한다.

디버깅

AVL 디버깅은 값 비교만 보는 것이 아니라, 중위 순서와 높이 정보와 balance 범위가 동시에 유지되는지 확인하는 과정이다
1
먼저 중위 순회 결과가 정렬 순으로 나오는지 확인해 BST property 가 살아 있는지 본다.
2
각 노드에서 왼쪽/오른쪽 높이 차이가 1을 넘지 않는지 확인한다.
3
회전 후 변경된 서브트리 루트가 부모와 루트 포인터에 정확히 반영됐는지 본다.
4
회전 직후 높이 갱신 순서가 자식 → 부모 순으로 맞는지 확인한다.
5
삭제 버그는 successor 치환 이후 재균형 구간에서 자주 나므로 그 구간을 따로 추적한다.
점검 체크리스트
- inorder 결과가 정렬 순인가
- 모든 노드의 높이 차이가 1 이하인가
- 회전 후 서브트리 루트 교체가 제대로 반영됐는가
- height / balance 값이 최신 상태인가
- 삭제 뒤 조상 방향 재균형이 빠지지 않았는가

요약

AVL 트리의 핵심은 BST의 검색 규칙을 유지한 채 각 노드의 높이 차이를 제한하고, 삽입·삭제 뒤 회전으로 그 균형을 복구해 최악의 경우에도 로그 높이를 보장하는 데 있다
  • ✅ AVL 트리는 height-balanced binary search tree 다.
  • ✅ 각 노드의 두 서브트리 높이 차이는 최대 1이어야 한다.
  • ✅ 검색은 BST와 같고, 수정 뒤에만 rotation 으로 균형을 복구한다.
  • ✅ 회전은 BST의 in-order 순서를 보존한 채 높이를 조정하는 국소 연산이다.
  • ✅ lookup, insert, delete 는 로그 높이 범위의 성능을 기대할 수 있다.
  • ✅ 삽입은 BST 삽입 + 재균형, 삭제는 BST 삭제 + 조상 방향 재균형으로 이해하는 편이 좋다.
  • ✅ AVL는 plain BST보다 안정적이지만 구현 복잡도는 더 높다.
  • ✅ 직접 구현할 때는 회전 코드보다 불변식 검증 코드를 먼저 준비하는 편이 안전하다.

 

 

728x90