AVL트리는 발명자의 이름인 Geory Adelson-Velsky and Evgeii LanDis을 따서 지어졌습니다.
스스로 균형을 맞추는 이진 탐색 트리여서 self-balancing binary search tree 라고 부르기도 합니다.
이진 트리에서, 데이터가 마치 연결리스트처럼 한줄로 쭉 나열되는 최악의 경우 트리의 이점을 살리지 못한다는 단점이 있었습니다
바로 아래의 그림처럼 말이죠.
이런 일이 발생하지 않기 위해서는 데이터를 추가할 때 한쪽으로 몰리지 않게 조절해주는 기능이 필요합니다.
이것을 가능하게 한 것이 바로 AVL 트리 입니다.
먼저 이진탐색트리의 코드부터 보겠습니다.
이진탐색트리 클래스)
연습을 위해 동일한 기능을 하는 함수를 재귀를 사용한 것과 사용하지 않은 것 둘다 구현했습니다.
template <typename K, typename V>
class BinarySearchTree {
public:
struct Item {
K key = K();
V value = V();
};
struct Node {
Item item;
Node* left = nullptr;
Node* right = nullptr;
};
Item* RecurGet(const K& key) { return RecurGet(root_, key); }
Item* RecurGet(Node* node, const K& key) {
if (!node) return nullptr;
if (key < node->item.key) return RecurGet(node->left, key);
if (key > node->item.key) return RecurGet(node->right, key);
return &node->item;
}
Item* IterGet(const K& key) { return IterGet(root_, key); }
Item* IterGet(Node* node, const K& key) {
if (!node) return nullptr;
Node* temp = node;
while (temp) {
if (key < temp->item.key)
temp = temp->left;
else if (key > temp->item.key)
temp = temp->right;
else
break;
}
return &temp->item;
}
void Insert(const Item& item) {
std::cout << "Insert " << item.key << item.value << '\n';
root_ = Insert(root_, item);
}
Node* Insert(Node* node, const Item& item) {
if (!node) return new Node{item, nullptr, nullptr};
if (node->item.key > item.key)
node->left = Insert(node->left, item);
else if (node->item.key < item.key)
node->right = Insert(node->right, item);
else
node->item = item;
return node;
}
void IterInsert(const Item& item) {
std::cout << "IterInsert " << item.key << item.value << '\n';
root_ = IterInsert(root_, item);
}
Node* IterInsert(Node* node, const Item& item) {
if (!node) return new Node{item, nullptr, nullptr};
Node* temp = node;
Node* prev = node;
int check = -1;
while (temp) {
if (item.key > temp->item.key) {
prev = temp;
temp = temp->right;
check = 0;
} else if (item.key < temp->item.key) {
prev = temp;
temp = temp->left;
check = 1;
}
}
if (check == 0) {
prev->right = new Node{item, nullptr, nullptr};
} else if (check == 1) {
prev->left = new Node{item, nullptr, nullptr};
}
return node;
}
Node* MinKeyLeft(Node* node) {
assert(node);
while (node->left) {
node = node->left;
}
return node;
}
void Remove(const K& key) {
std::cout << "Remove " << key << '\n';
root_ = Remove(root_, key);
}
Node* Remove(Node* node, const K& key) {
if (!node) return nullptr;
if (key < node->item.key)
node->left = Remove(node->left, key);
else if (key > node->item.key)
node->right = Remove(node->right, key);
else {
if (!node->left) {
Node* temp = node->right;
delete node;
return temp;
} else if (!node->right) {
Node* temp = node->left;
delete node;
return temp;
}
Node* temp = MinKeyLeft(node->right);
node->item = temp->item;
node->right = Remove(node->right, temp->item.key);
}
return node;
}
int Height() { return Height(root_); }
int Height(Node* node) {
if (!node) return 0;
return 1 + std::max(Height(node->left), Height(node->right));
}
protected:
Node* root_ = nullptr;
};
코드의 삽입 부분인 Insert 함수를 보면 알 수 있듯이 균형을 맞추지 않고 데이터를 삽입하는 것을 확인할 수 있습니다.
이를 방지하는 AVL트리에 대해 알아봅시다.
AVL트리의 특징 ( 이진 탐색 트리에서 달라진 점 )
1. Balance라는 함수를 이용하여 균형을 맞춥니다.
- Balance = 왼쪽 트리의 높이 (height(node->left)) - 오른쪽 트리의 높이 (height(node->right))
int Balance(Node* node){
if(node) return Height(node->left) - Height(node->right);
eles return 0;
}
2. 균형이 맞지 않으면 균형을 맞춰줍니다. ( RotateRight , RotateLeft 함수 이용 )
-RotateRight 예시와 코드
부모 노드 (여기서는 Z) 의 왼쪽 자식을 temp로 지정.
오른쪽으로 회전한 뒤 temp를 새로운 부모노드로 return.
Node* RotateRight(Node* parent) {
Node* temp = parent->left;
parent->left = temp->right;
temp->right = parent;
return temp;
}
3. 균형이 맞추어져 있기에 삽입, 삭제, 탐색의 평균/최악 시간복잡도 모두 O(log N)입니다.
AVL Tree 코드)
이진탐색트리에 균형을 맞추는 기능만 추가된 것이므로 위의 BinarySearchTree.h 를 include 하고
이진탐색트리 클래스를 상속받아 그대로 사용. (균형 맞추는 기능만 추가)
#include "BinarySearchTree.h"
template <typename K, typename V>
class AVL : public BinarySearchTree<K, V> {
public:
using Base = BinarySearchTree<K, V>;
using typename BinarySearchTree<K, V>::Item;
using typename BinarySearchTree<K, V>::Node;
int Height(Node* node) { return Base::Height(node); }
int Balance(Node* node) {
if (node)
return Base::Height(node->left) - Base::Height(node->right);
else
return 0;
}
Node* RotateRight(Node* parent) {
Node* temp = parent->left;
parent->left = temp->right;
temp->right = parent;
return temp;
}
Node* RotateLeft(Node* parent) {
Node* temp = parent->right;
parent->right = temp->left;
temp->left = parent;
return temp;
}
void Insert(const Item& item) { root_ = Insert(root_, item); }
Node* Insert(Node* node, const Item& item) {
if (!node) return new Node{item, nullptr, nullptr};
const auto& key = item.key;
if (key < node->item.key)
node->left = Insert(node->left, item);
else if (key > node->item.key)
node->right = Insert(node->right, item);
else {
node->item = item;
return node;
}
int balance = Balance(node);
if (balance == 0 || balance == 1 || balance == -1) return node;
if (balance > 1 && Balance(node->left) >= 0) {
return RotateRight(node);
} else if (balance < -1 && Balance(node->right) <= 0) {
return RotateLeft(node);
} else if (balance > 1 && Balance(node->left) <= -1) {
node->left = RotateLeft(node->left);
return RotateRight(node);
} else if (balance < -1 && Balance(node->right) >= 1) {
node->right = RotateRight(node->right);
return RotateLeft(node);
}
return node;
}
Node* MinKeyNode(Node* node) {
while (node->left) node = node->left;
return node;
}
void Remove(const K& key) { root_ = Remove(root_, key); }
Node* Remove(Node* node, const K& key) {
if (!node) return node;
if (key < node->item.key) {
node->left = Remove(node->left, key);
} else if (key > node->item.key) {
node->right = Remove(node->right, key);
} else {
if (!node->right) {
Node* temp = node->left;
delete node;
return temp;
} else if (!node->left) {
Node* temp = node->right;
delete node;
return temp;
}
Node* temp = MinKeyNode(node->right);
node->item = temp->item;
node->right = Remove(node->right, temp->item.key);
}
int balance = Balance(node);
if (balance == 0 || balance == 1 || balance == -1) return node;
if (balance > 1 && Balance(node->left) >= 0)
return RotateRight(node);
else if (balance > -1 && Balance(node->right) <= 0)
return RotateLeft(node);
else if (balance > 1 && Balance(node->left) <= -1) {
node->left = RotateLeft(node->left);
return RotateRight(node);
} else if (balance < -1 && Balance(node->right) >= 1) {
node->right = RotateLeft(node->right);
return RotateRight(node);
}
return node;
}
private:
Node*& root_ = BinarySearchTree<K, V>::root_;
};
코드를 보시고 이해가 안되신다면 아래 사이트에 들어가 어떻게 회전하는지, 어떻게 삭제되는지 시각적으로 확인하실 수 있으니
꼭 한번 들어가보시는 것을 추천드립니다.
https://www.cs.usfca.edu/~galles/visualization/AVLtree.html
'DataStructure' 카테고리의 다른 글
2-3Tree (2-3트리) (0) | 2024.09.25 |
---|---|
AVL트리를 이용한 영어사전 만들기 (2) | 2024.09.25 |
BinarySearch (이진탐색) (0) | 2024.06.25 |
SequentialSearch (순차 탐색) (0) | 2024.06.22 |
InsertionSort (삽입 정렬) (0) | 2024.06.20 |