당신은 주제를 찾고 있습니까 “avl 트리 삭제 – [코드라떼] 자바 자료구조 – AVL 트리 개념“? 다음 카테고리의 웹사이트 https://chewathai27.com/you 에서 귀하의 모든 질문에 답변해 드립니다: https://chewathai27.com/you/blog. 바로 아래에서 답을 찾을 수 있습니다. 작성자 코드라떼 이(가) 작성한 기사에는 조회수 2,951회 및 좋아요 55개 개의 좋아요가 있습니다.
- 삭제 할 노드가 leaf노드일 경우 1-1) 해당 노드를 삭제
- 삭제 할 노드가 한 개의 자식 노드를 가지고 있는 경우 2-1) 자식 노드를 삭제할 노드의 부모에 연결 2-2) 삭제할 노드를 제거
- 삭제 할 노드가 두 개의 자식 노드를 가지고 있는 경우
avl 트리 삭제 주제에 대한 동영상 보기
여기에서 이 주제에 대한 비디오를 시청하십시오. 주의 깊게 살펴보고 읽고 있는 내용에 대한 피드백을 제공하세요!
d여기에서 [코드라떼] 자바 자료구조 – AVL 트리 개념 – avl 트리 삭제 주제에 대한 세부정보를 참조하세요
AVL 트리는 스스로 균형 잡는 트리 중 하나로 이진 탐색 트리의 단점을 보완하기 위한 트리입니다. 이진 탐색 트리만 정확히 알고 구현할 수 있다면 AVL 트리를 구현하는 것은 어렵지 않습니다. 이번 강의에서는 AVL 트리에 대해서 알아봅시다.
다른 강의도 보고 싶다면 ‘코드라떼’ 에서 들어보세요!
강의와 관련된 추가노트, 온라인 실습도구를 제공합니다!
더 많은 자바 자료구조 강의를 들어 보고 싶다면
코드라떼에서 바로 보기!
https://www.codelatte.io/courses/java_data_structure
avl 트리 삭제 주제에 대한 자세한 내용은 여기를 참조하세요.
AVL Tree (2) – 삭제 – 개발자 노트
AVL Tree의 표현과 삽입은 이전 블로그를 참고하자. private Node delete(Node root, int key) {.
Source: devidea.tistory.com
Date Published: 1/26/2021
View: 1559
자료구조 AVL트리 생성연산 삭제연산 – 네이버 블로그
삽입이 일어났을 때 불균형이 발생하면 회전연산으로 균형을 맞추어야 하기 때문이며 삭제연산의 경우에도 마찬가지일 것이다. #자료구조 #AVL트리 #avl …
Source: m.blog.naver.com
Date Published: 10/20/2022
View: 4803
AVL Tree를 알아보자
이진트리라고 생각하고 데이터를 삽입하면 됩니다. 삽입할 데이터가 기준 노드보다 작다면 왼쪽, 크다면 오른쪽으로 이동합니다. 반복하다가 비어있는 자리 …
Source: velog.io
Date Published: 10/25/2021
View: 9617
[알고리즘] AVL Tree(트리) : 필수기본정리 – Balanced Factor, 4 …
목차 AVL 트리 개념 우선 AVL 트리는 이진 탐색 트리를 기초로 합니다. … AVL Tree는 자동으로 균형 잡힌 트리를 제공하기 때문에, 탐색, 삽입, 삭제 오퍼레이션들은 …
Source: underdog11.tistory.com
Date Published: 8/3/2022
View: 8915
AVL Tree – 더블에스 devlog
AVL 트리의 삽입연산은 2단계로 수행된다. 1단계에서는 이진탐색트리와 동일하게 삽입연산을 수행한다. 2단계에서는 새로 삽입된 노드로부터 루트 노트로 …
Source: doublesprogramming.tistory.com
Date Published: 7/30/2022
View: 3624
AVL 트리(AVL Tree) – LimeCoding
그러나 완전 이진 탐색 트리의 단점은 트리에 자료가 삽입될 때마다 완전 이진 탐색 트리의 형태를 유지하기 위해 트리의 모양을 바꾸어야 한다. 즉, 삽입 …
Source: limecoding.tistory.com
Date Published: 7/7/2022
View: 107
AVL 트리(높이 균형 이진 탐색 트리) 개념과 삽입 연산
AVL 트리(높이 균형 이진 탐색 트리). : 모든 노드의 왼쪽과 오른쪽 부분트리의 높이 차이가 1 이하인 이진탐색 트리. 삽입, 삭제 연산을 하여 트리 …
Source: hyunah-home.tistory.com
Date Published: 12/19/2022
View: 4857
9. AVL 트리 – 백지오
AVL 트리에서 원소의 삭제와 삽입은 이진 탐색 트리와 유사하나, 삽입 혹은 삭제로 인해 AVL 트리의 높이균형 속성이 파괴될 수 있으므로 이를 복원 …
Source: skyil.tistory.com
Date Published: 12/1/2021
View: 1741
AVL Tree – 평범한 게임 개발자
AVL 트리는 삽입, 삭제 연산시 균형이 깨질 수 있는데, 이때 회전(rotation) 연산을 통해 트리를 재구성하여 AVL 트리의 균형 성질을 유지시킨다.
Source: gamedevlog.tistory.com
Date Published: 2/29/2022
View: 5607
주제와 관련된 이미지 avl 트리 삭제
주제와 관련된 더 많은 사진을 참조하십시오 [코드라떼] 자바 자료구조 – AVL 트리 개념. 댓글에서 더 많은 관련 이미지를 보거나 필요한 경우 더 많은 관련 기사를 볼 수 있습니다.
주제에 대한 기사 평가 avl 트리 삭제
- Author: 코드라떼
- Views: 조회수 2,951회
- Likes: 좋아요 55개
- Date Published: 2021. 8. 27.
- Video Url link: https://www.youtube.com/watch?v=9BiHgy40NNE
AVL 트리 (AVL Tree)
AVL 트리
AVL트리는 트리가 한쪽으로 치우쳐 자라나는 현상을 방지하여 트리 높이의 균형을 유지하는 이진탐색트리
노드의 두 하위 트리(왼쪽, 오른쪽)의 높이의 차이가 최대 1을 넘지 않음
엄격하게 균형을 유지하기 때문에 Red-black 트리보다 더 빠른 성능을 가지지만 더 많은 작업이 필요
대부분의 연산은 이진탐색트리와 동일
삽입 삭제 연산은 트리가 균형을 유지하는지 확인
Linked List, Binary Search Tree, Balanced Binary Tree 차이점
1. Linked List
– 구현이 쉬움
– 많은 포인터(참조)를 저장
– O(N) 시간복잡도를 가짐
2. Binary Search Tree
– 탐색의 O(N) 시간복잡도를 O(logN) 시간복잡도로 줄임
– Unbalanced Tree일 경우 탐색의 속도는 느려짐
3. Balanced Binary Tree(AVL Tree, Red-Black Tree)
– 균형을 이루도록 보장
– 항상 O(logN)의 시간복잡도를 보장
시간 복잡도
이진 탐색 트리
공간 : 평균 O(N), 최악 O(N)
삽입 : 평균 O(logN), 최악(N)
삭제 : 평균 O(logN), 최악(N)
탐색 : 평균 O(logN), 최악(N)
AVL 트리
공간 : 평균 O(N), 최악 O(N)
삽입 : 평균 O(logN), 최악(logN)
삭제 : 평균 O(logN), 최악(logN)
탐색 : 평균 O(logN), 최악(logN)
탐색
이진트리와 동일한 방식으로 진행
최솟값 찾기 => 왼쪽 노드 방향으로 탐색
최댓값 찾기 => 오른쪽 노드 방향으로 탐색
https://walbatrossw.github.io/data-structure/2018/10/26/ds-avl-tree.html https://walbatrossw.github.io/data-structure/2018/10/26/ds-avl-tree.html
높이
https://walbatrossw.github.io/data-structure/2018/10/26/ds-avl-tree.html
높이 : 특정 노드부터 leaf 노드까지 가장 긴 경로의 길이
height = max(leftChild.height(), rightChild.height()) + 1
불균형 상태 : 하나의 노드를 기준으로 양쪽 서브트리의 높이 차이가 2 이상인 경우
불균형 상태인 경우 회전을 통해서 균형 상태로 맞춤
회전
삽입, 삭제 연산을 수행할 때 트리의 균형을 유지하기 위해 LL-회전, RR-회전, LR-회전, RL-회전연산을 사용
왼쪽 방향의 서브트리가 높아서 불균형이 발생할 때 서브트리를 오른쪽 방향으로 회전
오른쪽 방향의 서브트리가 높아서 불균형이 발생했을 때 왼쪽 방향으로 회전
회전을 하더라도 중위 순회를 통한 노드 순회의 순서는 동일.
트리 구조의 불균형을 맞추는 작업만을 수행
1. 왼쪽으로 트리가 치우쳐진 경우 – LL
https://walbatrossw.github.io/data-structure/2018/10/26/ds-avl-tree.html
왼쪽 노드와 오른쪽 노드의 높이 차를 구함
A의 왼쪽 = -1, 오른쪽 = -1, 차 = 0
B의 왼쪽 = 0, 오른쪽 = -1, 차 = 1
C의 왼쪽 = 1, 오른쪽 = -1, 차 = 2
높이의 차가 2이상인 경우 불균형임을 판단 할 수 있음
왼쪽으로 치우쳐진 트리를 오른쪽으로 회전함
2. 오른쪽으로 트리가 치우쳐진 경우- RR
https://walbatrossw.github.io/data-structure/2018/10/26/ds-avl-tree.html
오른쪽으로 치우쳐진 트리를 왼쪽으로 회전함
3. 왼쪽 자식노드에 오른쪽 자식 노드만 있는 트리 – LR
https://walbatrossw.github.io/data-structure/2018/10/26/ds-avl-tree.html
왼쪽 자식노드에 오른쪽 자식 노드만 있는 경우
1. 왼쪽 자식 노드를 왼쪽으로 회전
2. 왼쪽으로 트리가 치우쳐진 모양이 되었으므로 오른쪽으로 회전
4. 오른쪽 자식 노드에 왼쪽 자식 노드만 있는 트리 – RL
https://walbatrossw.github.io/data-structure/2018/10/26/ds-avl-tree.html
오른쪽 자식 노드에 왼쪽 자식 노드만 있는 경우
1. 오른쪽 자식 노드를 오른쪽으로 회전
2. 오른쪽으로 트리가 치우쳐진 모양이 되었으므로, 왼쪽으로 회전
삽입
1. 이진 탐색 트리와 동일하게 삽입을 진행
2. 삽입한 노드부터 루트 노드까지 높이를 측정
3. 높이를 통해 불균형 노드일 경우 모양에 따라 회전 수행
4. 불균형이 없을 때까지 2~3을 반복
삭제
1. 삭제 할 노드가 leaf노드일 경우
1-1) 해당 노드를 삭제
2. 삭제 할 노드가 한 개의 자식 노드를 가지고 있는 경우
2-1) 자식 노드를 삭제할 노드의 부모에 연결
2-2) 삭제할 노드를 제거
3. 삭제 할 노드가 두 개의 자식 노드를 가지고 있는 경우
3-1) 왼쪽 노드의 가장 큰 값 또는 오른쪽 노드의 가장 작은 값을 삭제할 노드와 위치를 변경
3-2) 삭제할 노드를 제거
https://walbatrossw.github.io/data-structure/2018/10/26/ds-avl-tree.html
ex) 32를 제거할 경우
왼쪽 노드에 가장 큰 값인 23과 위치를 변경
바꾼 후 32 노드 제거
오른쪽 노드의 가장 작은 값 55에 해당 작업 수행 가능
삭제한 후에 노드가 불균형일 경우 회전 작업을 수행
728×90
AVL Tree (2)
AVL Tree의 노드를 삭제한 이후에도 재배열 과정을 거쳐야 한다.
재배열 과정에서 지켜야할 규칙은 이전 블로그 에서 기록한 내용과 동일하다.
두 트리에서 지켜야 할 데이터 크기의 순서는 아래와 같다.
두 트리에서 지켜야 할 데이터 크기의 순서는 아래와 같다.
T1, T2, T3는 루트노드 y 또는 x의 자식트리이다. (y는 왼쪽, x는 오른쪽 트리)
T1, T2, T3는 루트노드 y 또는 x의 자식트리이다. (y는 왼쪽, x는 오른쪽 트리)
삭제의 순서
노드 w를 삭제한다고 할 때, 순서는 아래와 같다.
a) y는 z의 왼쪽자식, x는 y의 왼쪽자식 (left left)
b) y는 z의 왼쪽자식, x는 y의 오른쪽자식 (left right)
c) y는 z의 오른쪽자식, x는 y의 오른쪽자식 (right right)
d) y는 z의 오른쪽자식, x는 y의 왼쪽자식 (right left)
[그림 1] AVL Tree 삭제 예시 [그림 1]은 AVL Tree의 삭제 과정을 보여준다. 32을 삭제한 이후, 불균형한 첫번째 노드 44(z)을 찾았고, 깊이가 높은 자식 노드 62(y), 손자노드 78(x)을 찾아 right rkght case의 재배열 과정을 거쳤다. 재배열 시 left rotation을 수행하여 균형을 갖춘 AVL Tree의 성질을 유지하였다.아래 코드는 삭제를 수행하는 부분만 구현된 부분이다. AVL Tree의 표현과 삽입은 이전 블로그를 참고하자.
자료구조 AVL트리 생성연산 삭제연산
균형을 맞추는 함수를 만들었으니 이제 삽입하는 함수만 만들면 된다. put메소드는 이진탐색트리 때의 삽입과 거의 비슷하다. 그 때는 탐색연산을 통해 삽입이 되야할 위치를 찾고 빈 자리에 노드를 추가했는데 그 때처럼 하면 된다. 루트노드가 없으면 AVL트리에 n을 추가한다. 만약 다른 노드들이 있다면 생성할 위치를 찾아야 한다. n은 avl트리에 자리잡고 있는 노드, 그리고 key는 새로 추가할 노드의 key이다. n.key보다 작으면 n의 왼쪽으로 가서 함수를 재귀호출한다. 재귀호출을 통해 n은 n의 왼쪽노드가 된다. 그리고 또 비교를 하는 것이다. n.key와 key값을 말이다. 그렇게 계속해서 검사를 반복하다 보면 끝에 다다른다. 끝에는 노드가 없다. 따라서 루트노드를 추가했던 if n==None이라는 블럭에서 걸린다. 그러면 그 자리에 노드가 추가된다. 만약 같을 경우 이미 트리에 있으므로 value를 갱신한다. 노드가 추가되었기 때문에 높이를 증가시키고 balance함수를 호출해 균형을 맞춘다.
[알고리즘] AVL Tree(트리) : 필수기본정리 – Balanced Factor, 4가지 Rotation(회전), 삽입, 제거, Kotlin구현
반응형
목차
AVL 트리 개념
우선 AVL 트리는 이진 탐색 트리를 기초로 합니다. 이진 탐색 트리에 대하여 자세히 알고 싶으시다면 아래 링크를 확인해주세요
위 포스트를 보셨다면 이진 탐색트리의 단점은 불균형한 트리 모형을 하고 있을 때 트리의 성능이 O(log n)에서 O(n)으로 떨어진다는 것을 알 수 있었습니다.
불균형/균형 이진 탐색트리에 5까지 도달하는 과정
AVL트리는 이러한 문제를 해결하기위해 만들어졌습니다. 이진트리이면서, 균형을 유지하기 때문에 이진 검색 시 효율성을 보장하게 됩니다.
Balanced Tree (균형트리)
항상 균형트리의 모양을 하고 있는 게 AVL 트리의 특징입니다.
우선 균형트리에는 3가지 상태가 있습니다.
1. 완전균형상태(Perfect Balance)
2. 충분한균형상태(Good Enough Balance)
3. 불균형상태(Unbalance)
완전 균형 상태(Perfect Balance)
완전 균형 상태는 모든 레벨에서 노드가 가득 차있는 상태입니다.
충분한 균형 상태(Good Enough Balance)
가장 아래쪽 레벨을 제외하고 모든 노드가 채워져 있는 상태에 트리일 때입니다.
불균형 상태(Unbalance)
나머지 트리 모양은 불균형한 상태의 트리라 정의하고 이 트리들은 성능 저하의 원인입니다. 이 문제를 해결하기 위해 AVL Tree를 사용하게 됩니다. AVL Tree는 자동으로 균형 잡힌 트리를 제공하기 때문에, 탐색, 삽입, 삭제 오퍼레이션들은 O(log n)으로 진행할 수 있게 합니다.
BalanceFactor를 이용한 균형/ 불균형 트리 구분
먼저 AVL트리를 만들기 전에 현재 생성되고 있는 트리가 균형인지 불균형인지 파악해야 합니다. 균형인지 파악하기 위해 트리의 Height을 알아야 합니다. Height은 현재 노드로부터 단말 노드까지의 거리를 Height이라고 합니다. 아래 다이어그램은 각 노드에 Height을 표기하였습니다. 트리 용어와 익숙하시지 않으시다면 아래 링크를 확인해주세요.
파란 글씨는 Tree에 Height을 나타냅니다. 트리의 Height을 이용해서 균형 상태를 판별하게 됩니다. Height을 통해 균형을 판별하는 BalanceFactor라는 코드를 작성해보겠습니다.
Balance Factor
var height = 0 val leftHeight: Int get() = leftChild?.height?: -1 //leftchild가 없다면 -1을 반환 val rightHeight: Int get() = rightChild?.height? : -1 //rightChild가 없다면 -1을 반환 val balaneFactor: Int get() = leftHeight – rightHeight
BalanceFactor는 왼쪽, 오른쪽 자식의 height을 비교하여, 균형 트리인지 불균형 트리인지 구분하게 됩니다. balanceFactor를 이용해서 어떻게 구분할 수 있을까요? 노드의 Balance Factor에 절댓값이 1 이하이면 balance라고 정의할 수 있습니다.
위 다이어그램은 균형 트리입니다. 초록 글씨는 BalancFactor를 나타내고 파란 글씨는 현재 노드의 Height을 의미합니다.
25의 balancefactor가 -1인 이유는 무엇일까요? 빈 노드는 -1로 카운트합니다. 그래서 비어있는 왼쪽 자식 노드(-1) height이 0 인 자식 노드 두 개를 이용해 25의 Balance Factor을 구하면 -1이 나오게 됩니다.
위 다이어그램은 불균형 트리입니다. 보다시피 height을 나타내는 초록색 글씨 중 -2가 있기 때문입니다. -2의 절댓값은 2이므로 1을 초과합니다. 그러므로 불균형 트리임을 확인할 수 있습니다.
Rotation(회전)
이제 우리는 어떤 게 불균형한 지 구별 가능해졌기 때문에 이 불균형 트리들을 AVL트리를 만들기 위해 균형 트리로 바꿔줘야 합니다. 이때 우리는 rotation을 활용하여 균형트리로 바꿔줘야합니다.
rotation에는 4가지 종류가 있습니다.
1. left left
2. right right
3. left-right
4. right-left
LL Rotation
left- left를 쓰는 때는 불균형하면서 leftheavy일 때 left-left회전을 쓰게 됩니다. left-left 회전의 원리는 아래와 같습니다.
balanceFactor가 -2이기 때문에 AVL 트리 조건에 위배되게 됩니다. 따라서 오른쪽 방향으로 회전을 통해 모든 균형 값을 0으로 만들 수 있습니다. 이렇게 leftHeavy인 경우에는 오른쪽으로 1 회전해주면 됩니다. 회전을 하는 법은 가운데 노드를 부모 노드로 만들고 나머지 두 노드를 자식 노드로 만들면 됩니다.
코드로 구현하면 아래와 같습니다.
오른쪽 회전 코드 구현
private fun rightRotate(node: AVLNode
):AVLNode { //1 val pivot = node.leftChild!! //2 node.leftChild = pivot.rightChild //3 pivot.rightChild = node //4 node.height = max(node.leftHeight, node.rightHeight) + 1 pivot.height = max(pivot.leftHeight, pivot.rightHeight) + 1 //5 return pivot } 코드를 설명해보겠습니다.
1. pivot이라는 value를 만들어줬는데 중심점을 뜻합니다. node에 왼쪽 자식을 pivot으로 만듭니다. 즉 위 다이어그램에서 0002를 pivot으로 지정하게 됩니다.
2. 노드에 leftChild위치에 pivot에 rightChild를 넣습니다. 다이어그램에서 0001을 node의 rightChild위치에 놓습니다.
3. 현재 pivot은 부모 node위치에 있습니다. 이 pivot노드에 right위치에 node위치에 있는 0003을 배치하면 됩니다.
4. 이제 높이를 업데이트해줌으로써, balancefactor가 0이 되도록 합니다.
5. 마지막으로 회전된 트리를 반환합니다.
RR Rotation
right-right을 사용할 때는 right heavy인 트리일 때 사용하면 됩니다.
위에서 설명했던 left-left와 방향만 다르고 방식은 같습니다. Right-heavy일 때는 왼쪽으로 1회 회전하면 됩니다.
왼쪽 회전 코드 구현
private fun leftRotate(node: AVLNode
): AVLNode { val pivot = node.rightChild!! node.rightChild = pivot.leftChild pivot.leftChild = node node.height = max(node.leftHeight, node.rightHeight) + 1 pivot.hegith = max(pivot.leftHeight, pivot.rightHeight) + 1 return pivot } LR 회전
left-right회전은 위에서 했던 회전과 조금 다릅니다. 회전을 두 번 해줘야 합니다. 아래 다이어그램을 확인해보겠습니다.
먼저 10 node아래에 10보다 더 큰 15 노드가 삽입되었습니다. 그리고 불균형한 트리가 되어버렸습니다. 균형 트리로 만들어주기위해 가장 먼저해야할것은, left 회전을 통해 right rotate을 할수있도록 정렬하는것입니다. 그리고 right roate을 하면 균형트리로 변환할 수 있습니다.
코드로 좀 더 자세히 살펴보겠습니다.
private fun leftRightRotate(node: AVLNode
): AVLNode { //1 val leftChild = node.leftChild ?: return node //2 node.leftChild = leftRotate(leftChild) //3 return rightRotate(node) } 1. 먼저 leftChild라는 변수를 node에 leftChild로 지정합니다. leftChild는 15입니다.
2. node.leftChild를 left을 하게 됩니다. leftRotate함수에 파라미터로 위에서 leftchild변수로 지정했던 15가 들어갑니다. 그러면 10과 15의 자리가 바뀌게 됩니다.
3. 마지막으로 rightRotate을 하면 15가 부모 노드가 되고 나머지 2개의 노드 10과 25가 자식 노드가 됩니다.
RL 회전
right left회전은 left-right과 방식은 같고 돌리는 방향 순서를 반대로 해주면 됩니다.
바로 코드로 확인해보겠습니다.
private fun rightLeftRotate(node: AVLNode
):AVLNode { val rightChild = node.rightChild?: return node node.rightChild = rightRotate(rightChild) return leftRotate(node) } leftRight에 정반대로 해주시면 됩니다.
이제 균형인지 불균형인지 구별하는 법과, left heavy일 때 right heavy일때 쓸 rotation까지 구현할 수 있게 되었습니다.
이제 이 함수들을 종합하여, 각기 다른 불균형 트리를 적절한 함수를 사용해 균형 트리를 자동으로 만들어주는 balanced함수를 구현해주겠습니다.
AVL 트리 구현
이제 AVL트리를 구현하기 위한 모든 준비가 되었습니다. 이제 위에서 만들었던 함수들을 종합하여 AVL Tree를 만들어보도록 하겠습니다.
Balanced함수 구현
Balanced함수는 균형 트리로 바꿔주기 위해 어떤 Rotation함수를 써야 할지 결정해줍니다
Balanced함수 구현
private fun balanced(node: AVLNode
): AVLNode { return when (node.balanceFactor) { 2-> { //1 if(node.leftChild?.balanceFactor ==-1) { leftRightRotate(node) } else{ //2 rightRotate(node) } } -2 -> { //3 if(node.rightChild?.balanceFactor ==1) { rightLeftRotate(node) } else { //4 leftRotate(node) } } else -> node } } 1. node의 balance factor가 2이면 right회전
2. node의 balance factor가 2이면서 node.leftChild가 -1이면 leftright 회전
3. node의 balance factor가 -2이면 left회전
4. node의 balance factor가 -2이면서 node.rightChild가 1이면 rightleft 회전
Insert함수 구현
insert함수는 기존 이진 탐색 트리와 balanced함수가 들어가는 것을 제외하면 똑같습니다.
insert함수 구현
private fun insert(node:AVLNode
?. value: T):AVLNode ? { node?: return AVLNode(value) if(value < node.value) { node.leftChild = insert(node.leftChild,value) } else{ node.rightChild = insert(node.rightChild, value) } val balancedNode = balanced(node) balancedNode?.height = max(balancedNode?.leftHeight?: 0, balancedNode?.rightHeight?: 0) +1 return balancedNode } 이렇게 AVL트리에 insert함수는 insert를 실행해줄 때마다, insert 된 후, balanced로 node들을 회전하여 재배열을 통해 균형 트리를 생성하는 것을 볼 수 있습니다. 이제 insert함수를 활용하여 AVL Tree를 만들어보겠습니다. "repeated insertions in sequenece" example{ val tree = AVLTree () (0..14).forEach{ tree.insert(it) } print(tree) } 결과는 다음과 같습니다.
—Example of repeated insertions in sequence— ┌──14 ┌──13 │ └──12 ┌──11 │ │ ┌──10 │ └──9 │ └──8 7 │ ┌──6 │ ┌──5 │ │ └──4 └──3 │ ┌──2 └──1 └──0
위 예제로는 insert를 이해하기 충분하지 않아서 아래 이미지를 추가합니다. 아래 이미지는 추가되는 과정을 다이어그램으로 설명한 것입니다. 트리에 node가 추가되는 방식은 이진 탐색 트리 포스트에서 설명하였습니다.
Remove함수 구현
이진 탐색 트리에서 remove에서 balanced함수만 더해주고 balanced 됐을 때 바뀐 위치들의 node의 높이만 업데이트해주면 됩니다.
val balancedNode = balanced(node) balancedNode.height = max( balancedNode.leftHeight, balancedNode.rightHeight ) + 1 return balancedNode
remove함수도 실행해보겠습니다.
val tree = AVLree
() tree.insert(15) tree.insert(10) tree.insert(16) tree.insert(18) print(tree) tree.remove(10) print(tree) 코드를 실행하면 아래와 같이 나옵니다.
—Example of removing a value— ┌──18 ┌──16 │ └──null 15 └──10 ┌──18 16 └──15
이렇게 하면 AVL Tree를 위한 함수들이 모두 업데이트됩니다.
반응형
AVL Tree
본 글은 Udemy의 자바 자료구조 강의를 듣고 개인적으로 학습한 내용 복습하기 위해 작성된 글로 내용상 오류가 있을 수 있습니다. 오류가 있다면 지적 부탁 드리겠습니다.
AVL Tree
1. AVL Tree?
AVL트리는 트리가 한쪽으로 치우쳐 자라나는 현상을 방지하여 트리 높이의 균형을 유지하는 이진탐색트리이다. Balanced 이진탐색트리를 만들면 N개의 노드를 가진 트리의 높이가 O(logN)이 되어 탐색, 삽입, 삭제 연산의 수행시간이 O(logN)이 보장된다.
1.1 Linked List, Binary Search Tree, Balanced Binary Tree 비교
AVL 트리에 대해 알아보기 전에 3가지 자료구조의 차이점에 대해 알아보자.
Linked List 구현이 쉬움 많은 포인터(참조)를 저장 O(N) 시간복잡도를 가짐
Binary Search Tree 탐색의 O(N) 시간복잡도를 O(logN) 시간복잡도로 줄임 Unbalanced Tree일 경우 탐색의 속도는 느려짐
Balanced Binary Tree(AVL Tree, Red-Black Tree) 균형을 이루도록 보장 항상 O(logN)의 시간복잡도를 보장
예를 들어 이진탐색트리에 정렬된 배열[1, 2, 3, 4]를 차례로 저장한다고 가정하면 아래와 같다.
이렇게 이진탐색트리에 정렬된 배열을 저장하면, 결국 연결리스트가 되고, 시간복잡도는 O(logN)이 아닌 O(N)이 된다.
1.2 AVL Tree의 특성
이진탐색트리 연산 실행시간은 이진탐색트리의 높이에 따라 달라지는데 최상의 성능을 얻으려면 트리의 균형을 유지해야한다.
AVL 트리에서 노드의 두 하위 트리(왼쪽, 오른쪽)의 높이의 차이가 최대 1을 넘지 않는다.
AVL 트리는 엄격하게 균형을 유지하기 때문에 Red-black 트리보다 더 빠른 성능을 가지지만 더 많은 작업을 수행해야만 한다.
특히 운영체제의 경우 이러한 자료구조에 의존한다.
대부분의 연산은 이진탐색트리와 동일하다.
이진탐색트리와 동일하게 모든 노드는 최대 2개의 자식노드를 가질수 있고, 왼쪽 자식노드는 부모 노드보다 작고, 오른쪽 자식노드는 크다.
삽입 연산의 경우 이진탐색트리와 동일하지만 모든 삽입연산은 트리가 균형을 유지하는지 확인을 해야한다.
삭제, 최대/최소값 반환 연산 또한 마찬가지이다.
2. AVL Tree 연산
2.1 연산의 시간복잡도(Time Complexity)
AVL 트리의 기본연산은 이진탐색트리와 동일하지만 아래의 표를 보면 시간복잡도가 다르다.
이진탐색트리 평균 최악 공간 O(N) O(N) 삽입 O(logN) O(N) 삭제 O(logN) O(N) 탐색 O(logN) O(N)
AVL트리 평균 최악 공간 O(N) O(N) 삽입 O(logN) O(logN) 삭제 O(logN) O(logN) 탐색 O(logN) O(logN)
AVL 트리의 탐색은 이진트리와 동일하다.
2.2.1 특정 노드 탐색
2.2.2 최소, 최대값 탐색
AVL 트리의 연산을 수행하고, 불균형이 발생하면 회전을 통해 균형을 다시 맞춘다. 이렇게 균형을 맞추기 위해서는 각 노드들의 높이를 알아야만 한다. 그렇기 때문에 각 노드의 높이를 계산하는 과정을 이해하는 것이 중요하다.
노드의 높이는 특정 노드부터 leaf 노드까지 가장 긴 경로의 길이를 말한다.
height = max(leftChild.height(), rightChild.height()) + 1
특정 노드의 높이를 계산하는 방법은 위와 같은 코드를 통해 계산을 할 수 있다.
특정노드의 왼쪽, 오른쪽 자식노드의 높이 중에서 가장 큰 값을 1을 더한 값이 특정 노드의 높이가 된다.
leaf 노드는 null인 자식노드를 가지고 있는데 이런 경우 자식 노드의 높이는 -1로 간주한다.
위의 그림을 보면 AVL 트리는 모든 노드의 왼쪽, 오른쪽 자식노드의 높이가 최대 +1, 최소 -1의 차이만을 허용한다는 것을 알 수 있다.
AVL 트리에서 삽입, 삭제 연산을 수행할 때 트리의 균형을 유지하기 위해 LL-회전, RR-회전, LR-회전, RL-회전연산이 사용된다. 각 회전연산은 두 종류의 기본적인 연산으로 구현된다.
rightRotate() : 오른쪽 방향으로 회전
: 오른쪽 방향으로 회전 leftRotate() : 왼쪽 방향으로 회전
rightRotate() 은 왼쪽 방향의 서브트리가 높아서 불균형이 발생할 때 서브트리를 오른쪽 방향으로 회전하기 위한 메서드이고, leftRotate() 는 오른쪽 방향의 서브트리가 높아서 불균형이 발생했을 때 왼쪽 방향으로 회전하기 위한 메서드이다.
위의 그림을 보면 오른쪽 또는 왼쪽 회전을 하더라도 중위 순회를 하면 노드를 순회하는 순서는 항상 같다. 즉, 회전을 통해 트리 구조의 불균형을 맞추는 작업만을 수행하는 것이다.
이제 구체적인 상황들을 통해 어떻게 회전을 수행하는지 알아보자.
그전에 앞서 노드의 높이를 통해 불균형상태인지 아닌지를 알아보는 계산식은 아래와 같다. 왼쪽, 오른쪽 하위트리의 높이 차이기 절대 값 1을 초과한 경우 불균형 상태이다.
| height(leftSubtree) – height(rightSubtree) | ≤ 1
2.4.1 Case 1 : 왼쪽으로 트리가 치우친 경우 – LL
위 트리는 왼쪽으로 치우친 상태인데 이 트리가 어떤 과정을 거쳐 균형 상태로 바뀌는지 살펴보자.
leaf 노드(A)는 자식노드가 없기 때문에 왼쪽, 오른쪽 자식노드를 -1로 간주하여 높이를 계산하면 0이고 균형상태이다. 그 다음 노드인 B는 왼쪽 노드는 0, 오른쪽 노드는 null이기떄문에 -1로 높이를 계산하면 1이고 균형상태이다. 루트 노드인 D는 왼쪽노드는 1, 오른쪽 노드는 null이기 때문에 -1로 높이를 계산하면 2이고 하위트리의 높이 차이가 1을 초과하기 때문에 불균형 상태이다. 왼쪽으로 치우친 트리의 균형을 잡기 위해 오른쪽으로 회전을 수행한다.
오른쪽으로 회전을 수행하는 알고리즘은 아래와 같다.
BEGIN rotateRight(Node node) Node tempLeftNode = node.getLeftNode(); // 상위 노드의 왼쪽 하위 노드 Node t = tempLeftNode.getRightNode(); // 왼쪽 하위 노드의 오른쪽 하위 노드 tempLeftNode.setRightNode(node); // 왼쪽 하위 노드의 오른쪽 하위 노드를 상위 노드로 변경 node.setLeftNode(t); // 상위 노드의 왼쪽 노드를 오른쪽 하위 노드로 변경 node.updateHeight(); // 상위 노드의 높이 변경 tempLeftNode.updateHeight(); // 왼쪽 하위 노드 높이 변경 END
2.4.2 Case 2 : 오른쪽으로 트리가 치우친 경우 – RR
위 트리는 오른쪽으로 트리가 치우친 상태인데 이 트리가 어떤 과정을 거쳐 균형 상태로 바뀌는지 살펴보자.
leaf 노드(E)는 자식노드가 없기 때문에 왼쪽, 오른쪽 자식노드는 -1로 간주하여 높이를 계산하면 0이고 균형상태이다. 그 다음 노드인 D는 왼쪽 노드는 null이기때문에 -1, 오른쪽 노드는 0이기떄문에 높이를 계산하면 1이고 균형상태이다. 루트 노드인 F는 왼쪽노드는 null이기 때문에 -1, 오른쪽 노드는 1로 높이를 계산하면 2이고 하위트리의 높이 차이가 1을 초과하기 때문에 불균형 상태이다. 오른쪽으로 치우친 트리의 균형을 잡기 위해 왼쪽으로 회전을 수행한다.
왼쪽으로 회전할 때의 알고리즘은 아래와 같다.
BEGIN rotateLeft(Node node) Node tempRightNode = node.getRightNode() // 상위노드의 오른쪽 하위 노드 Node t = tempRightNode.getLeftNode() // 오른쪽 하위 노드의 왼쪽 하위 노드 tempRightNode.setLeftNode(node) // 오른쪽 하위 노드의 왼쪽 하위노드를 상위 노드로 변경 node.setRightNode(t) // 상위 노드의 오른쪽 노드를 왼쪽 하위 노드로 변경 node.updateHeight() // 상위 노드의 높이 변경 tempRightNode.updateHeight() // 오른쪽 하위 노드의 높이 변경 END
2.4.3 Case 3 : 왼쪽 자식노드에 오른쪽 자식 노드만 있을 경우 – LR
위의 트리를 보면 루트노드는 왼쪽 자신노드만 있고, 왼쪽 자식노드는 오른쪽 자식노드만 있어 불균형 상태이다. 이러한 상태의 트리의 균형을 잡는 과정을 알아보자.
leaf 노드(C)는 자식 노드가 없기 때문에 왼쪽, 오른쪽 자식노드를 -1로 간주하여 높이를 계산하면 0이 되고, 균형상태이다. 노드 B는 왼쪽 자식노드는 null이기때문에 -1, 오른쪽 자식 노드는 0으로 높이를 계산하면 1이 되고, 균형상태이다. 루트 노드인 노드 D는 왼쪽 자식노드는 1, 오른쪽 자식노드는 null이기 때문에 -1로 높이를 계산하면 2이기 때문에 불균형 상태이다. 노드 B를 왼쪽으로 회전시킨다. 이렇게 되면 왼쪽으로 치우친 트리의 형태가 되는데 루트 노드인 D를 오른쪽으로 회전 시킨다.
2.4.4 Case 4 : 오른쪽 자식 노드에 왼쪽 자식 노드만 있을 경우 – RL
위의 트리를 보면 루트노드는 오른쪽 자신노드만 있고, 오른쪽 자식노드는 왼쪽 자식노드만 있어 불균형 상태이다. 이러한 상태의 트리의 균형을 잡는 과정을 알아보자.
leaf 노드(E)는 자식 노드가 없기 때문에 왼쪽, 오른쪽 자식노드를 -1로 간주하여 높이를 계산하면 0이 되고, 균형상태이다. 노드 F의 왼쪽 자식노드는 1, 오른쪽 자식 노드는 null이기때문에 -1로 높이를 계산하면 1이 되고, 균형상태이다. 루트 노드인 노드 D는 왼쪽 자식노드가 null이기때문에 -1, 오른쪽 자식노드 1로 높이를 계산하면 2이기 때문에 불균형 상태이다. 노드 F를 오른쪽으로 회전시킨다. 이렇게 되면 오른쪽으로 치우친 트리의 형태가 되는데 루트 노드인 D를 왼쪽으로 회전 시킨다.
AVL 트리의 삽입연산은 2단계로 수행된다. 1단계에서는 이진탐색트리와 동일하게 삽입연산을 수행한다. 2단계에서는 새로 삽입된 노드로부터 루트 노트로 거슬러 올라가며 각 노드의 높이를 갱신한다. 이때 불균형이 발생한 노드를 발견하면 새 노드가 어디에 삽입되었는가에 따라 회전연산을 수행한다.
avlTree.insert(12);
이진탐색 트리와 동일하게 삽입연산을 수행한다.
삽입연산이 완료되면 삽입한 노드부터 루트노드까지 높이를 계산을 수행하고, 불균형이 발생하면 회전을 수행한다. 회전이 끝나고 트리 모든 노드의 높이의 계산을 다시 수행하여, 불균형을 발견하면 회전연산을 다시 수행하고, 더이상 불균형이 없으면 연산을 종료한다.
그렇다면 이번에는 AVL 트리에 처음부터 데이터가 삽입되는 과정을 보자.
avlTree.insert(10); avlTree.insert(20); avlTree.insert(30); avlTree.insert(40); avlTree.insert(50); avlTree.insert(60);
AVL 삭제 연산 또한 마찬가지로 2단계로 수행된다. 1단계에서는 이진탐색트리와 동일하게 삭제연산을 수행한다. 2단계에서는 삭제된 노드로부터 루트 노드 방향으로 거슬러 올라가면서 불균형이 발생한 경우 회전연산을 수행한다.
이진탐색 트리와 동일하게 AVL 트리의 삭제 연산은 아래와 같이 3개의 경우의 수가 존재한다.
삭제할 노드가 leaf 노드인 경우 삭제할 노드가 한 개의 자식 노드를 가진 경우 삭제할 노드가 두 개의 자식 노드를 가진 경우
2.6.1 : 삭제할 노드가 leaf 노드일 경우
제거할 노드가 leaf 노드일 경우는 아래와 간단하게 제거하기만 하면 된다.
avlTree.remove(5);
2.6.2 : 삭제할 노드가 한 개의 자식노드를 가진 경우
제거할 노드가 하나의 자식 노드를 가진 경우는 아래와 같이 참조값을 변경하기만 하면 된다.
avlTree.remove(1);
2.6.3 : 삭제할 노드가 두 개의 자식노드를 가진 경우
제거할 노드가 2개의 자식 노드를 가진 경우 왼쪽 하위 트리에서 가장 큰 항목 또는 오른쪽 하위 트리에서 가장 작은 항목을 찾아 서로의 위치를 바꾸고, 노드를 제거한다.
왼쪽 하위 트리에서 가장 큰 항목을 찾아 제거했을 경우
오른쪽 하위 트리에서 가장 큰 항목을 찾아 제거했을 경우
avlTree.remove(32);
2.6.4 : 노드를 삭제 한 뒤 트리가 불균형일 때
노드를 삽입할 때와 마찬가지로 노드를 삭제한 뒤 트리가 불균형인지 확인한 뒤 불균형 상태라면 회전연산을 수행한다.
avlTree.remove(79);
3. AVL Tree 구현
3.1 클래스 작성
기본적으로 AVL Tree를 구현하기 위해 노드 클래스, 트리 인터페이스, 트리 인터페이스를 구현한 AVL 트리 클래스를 작성해준다.
// 노드 클래스 public class Node
{ private T data; // 데이터 private Node leftNode; // 왼쪽 하위노드 private Node rightNode; // 오른쪽 하위노드 private int height; // 높이 public Node(T data) { this.data = data; } public T getData() { return data; } public void setData(T data) { this.data = data; } public Node getLeftNode() { return leftNode; } public void setLeftNode(Node leftNode) { this.leftNode = leftNode; } public Node getRightNode() { return rightNode; } public void setRightNode(Node rightNode) { this.rightNode = rightNode; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } @Override public String toString() { return this.data.toString(); } } // 트리 인터페이스 public interface Tree
{ public void insert(T data); // 삽입 public void traverse(); // 순회 public void delete(T data); // 삭제 } // AVL 트리 클래스 public class AVLTree2
> implements Tree { private Node root; // 루트 노드 // 삽입 @Override public void insert(T data) { } // 순회 @Override public void traverse() { } // 삭제 @Override public void delete(T data) { } } 3.2 회전
// 특정 노드의 높이 반환 private int height(Node
node) { if (node == null) { return -1; } return node.getHeight(); } 회전을 수행하기 위해서는 노드의 높이들을 알기 위해 위와 같이 AVL 트리 클래스에 특정노드의 높이를 반환하는 메서드를 작성한다.
// 오른쪽 회전 private Node
rightRotation(Node parentNode) { System.out.println(“Rotating to the right on node : ” + parentNode); Node newParentNode = parentNode.getLeftNode(); // 왼쪽 자식노드가 새로운 부모노드가 됨 Node nullNode = newParentNode.getRightNode(); // 왼쪽 자식노드의 오른쪽 자식노드(null 노드) newParentNode.setRightNode(parentNode); // 새로운 부모노드의 오른쪽 자식노드에 기존의 부모노드를 세팅 parentNode.setLeftNode(nullNode); // 기존의 부모노드의 왼쪽 자식노드를 null 노드로 세팅 // 회전한 노드들의 높이 갱신 parentNode.setHeight(Math.max(height(parentNode.getLeftNode()), height(parentNode.getRightNode())) + 1); newParentNode.setHeight(Math.max(height(newParentNode.getLeftNode()), height(newParentNode.getRightNode())) + 1); // 새로운 부모노드 반환 return newParentNode; } // 왼쪽 회전 private Node
leftRotation(Node parentNode) { System.out.println(“Rotating to the left on node : ” + parentNode); Node newParentNode = parentNode.getRightNode(); // 오른쪽 자식노드가 새로운 부모노드가 됨 Node nullNode = newParentNode.getLeftNode(); // 오른쪽 자식노드의 왼쪽 자식노드(null 노드) newParentNode.setLeftNode(parentNode); // 새로운 부모노드의 왼쪽 자식노드에 기존의 부모노드를 세팅 parentNode.setRightNode(nullNode); // 기존의 부모노드의 오른쪽 자식노드를 null 노드로 세팅 // 회전한 노드들의 높이 갱신 parentNode.setHeight(Math.max(height(parentNode.getLeftNode()), height(parentNode.getRightNode())) + 1); newParentNode.setHeight(Math.max(height(newParentNode.getLeftNode()), height(newParentNode.getRightNode())) + 1); // 새로운 부모노드 반환 return newParentNode; } 3.3 삽입
삽입 메서드를 아래와 같이 작성해준다. 삽입 과정은 이진탐색 트리와 동일하게 처리하지만 삽입 연산이 완료되고, 트리의 노드들의 높이를 계산하여 트리가 불균형인지 판단하여 불균형이라면 회전연산을 수행한다.
// 삽입 @Override public void insert(T data) { root = insert(root, data); }
// 삽입 구현 private Node
insert(Node node, T data) { // root가 비어있거나 leaf 노드인 경우 새로운 노드 생성 if (node == null) { return new Node<>(data); } // 삽입할 데이터가 상위노드(부모노드)의 데이터보다 작으면 왼쪽 하위노드에 새로운 노드 생성 // 삽입할 데이터가 상위노드(부모노드)의 데이터보다 크면 오른쪽 하위노드에 새로운 노드 생성 if (data.compareTo(node.getData()) < 0) { node.setLeftNode(insert(node.getLeftNode(), data)); // 왼쪽 leaf 노드를 찾을때까지 재귀호출 } else { node.setRightNode(insert(node.getRightNode(), data)); // 오른쪽 leaf 노드를 찾을 때까지 재귀 호출 } // 삽입이 완료되고 노드의 높이를 갱신 node.setHeight(Math.max(height(node.getLeftNode()), height(node.getRightNode())) + 1); // 삽입으로 인해 트리가 불균형이면 회전연산(LL, RR, LR, RL) 수행 node = settleViolation(data, node); return node; } // 회전 수행 : 삽입시 private Node settleViolation(T data, Node node) { // 트리의 불균형 여부 파악 int balance = getBalance(node); // Left-Left : 높이 차이가 1보다 크고, 삽입된 데이터가 상위노드의 데이터보다 작은 경우 if (balance > 1 && data.compareTo(node.getData()) < 0) { System.out.println("Tree is left-left heavy."); return rightRotation(node); // 오른쪽 회전 수행 } // Right-Right : 높이 차이가 -1보다 작고, 삽입된 데이터가 상위노드의 데이터보다 큰 경우 if (balance < -1 && data.compareTo(node.getData()) > 0) { System.out.println(“Tree is right-right heavy.”); return leftRotation(node); // 왼쪽 회전 수행 } // Left-Right : 높이 차이가 1보다 크고, 삽입된 데이터가 상위노드의 데이터보다 큰 경우 if (balance > 1 && data.compareTo(node.getData()) > 0) { System.out.println(“Tree is left-right heavy.”); node.setLeftNode(leftRotation(node.getLeftNode())); // 왼쪽 회전 수행 return rightRotation(node); // 오른쪽 회전 수행 } // Right-Left : 높이 차이가 -1보다 작고, 삽입된 데이터가 상위노드의 데이터보다 작은 경우 if (balance < -1 && data.compareTo(node.getData()) < 0) { System.out.println("Tree is right-left heavy."); node.setRightNode(rightRotation(node.getRightNode())); // 오른쪽 회전 수행 return leftRotation(node); // 왼쪽 회전 수행 } return node; } // 트리 균형/불균형 여부 판단 private int getBalance(Node node) { // 트리가 비어있는 상태 if (node == null) { return 0; } // 계산 결과가 1보다 큰 경우 : LL or LR // 계산 결과가 -1보다 작은 경우 : RR or RL return height(node.getLeftNode()) – height(node.getRightNode()); } 3.4 순회
AVL 트리는 이진탐색 트리의 순회와 같다.
// 순회 @Override public void traverse() { if (root == null) { return; } System.out.print(“inorder traversal : “); inOrderTraversal(root); System.out.println(); System.out.print(“preorder traversal : “); preOrderTraversal(root); System.out.println(); System.out.print(“postorder traversal : “); postOrderTraversal(root); }
// 중위 순회 private void inOrderTraversal(Node
node) { if (node.getLeftNode() != null) { inOrderTraversal(node.getLeftNode()); } System.out.print(node + ” ==> “); if (node.getRightNode() != null) { inOrderTraversal(node.getRightNode()); } } // 전위 순회 private void preOrderTraversal(Node
node) { System.out.print(node + ” ==> “); if (node.getLeftNode() != null) { inOrderTraversal(node.getLeftNode()); } System.out.print(node + ” ==> “); if (node.getRightNode() != null) { inOrderTraversal(node.getRightNode()); } } // 후위 순회 private void postOrderTraversal(Node
node) { if (node.getLeftNode() != null) { inOrderTraversal(node.getLeftNode()); } if (node.getRightNode() != null) { inOrderTraversal(node.getRightNode()); } System.out.println(node); } 3.5 삭제
삭제 연산은 아래와 같이 코드를 작성하면된다. 삭제 연산도 이진탐색트리와 동일하지만 삭제 연산이 완료되면 트리의 각노드의 높이를 계산해 트리의 불균형이 발생한 경우 각각의 상황에 맞게 회전연산을 수행한다.
// 삭제 @Override public void delete(T data) { root = delete(root, data); }
// 삭제 구현 private Node
delete(Node node, T data) { if (node == null) { return node; } // 삭제할 노드 탐색 // 삭제할 노드의 데이터가 부모노드의 데이터보다 작은 경우 if (data.compareTo(node.getData()) < 0) { node.setLeftNode(delete(node.getLeftNode(), data)); // 왼쪽 자식노드 방향으로 삭제 재귀호출 // 삭제할 노드의 데이터가 부모노드의 데이터보다 큰 경우 } else if (data.compareTo(node.getData()) > 0) { node.setRightNode(delete(node.getRightNode(), data)); // 오른쪽 자식노드 방향으로 삭제 재귀호출 // 삭제할 노드를 찾은 경우 } else { // 1. 삭제할 노드가 leaf 노드인 경우 if (node.getLeftNode() == null && node.getRightNode() == null) { System.out.println(“Removing a leaf node…”); return null; } // 2. 삭제할 노드가 하나의 자식노드를 가진 경우 if (node.getLeftNode() == null) { System.out.println(“Removing the right child node”); Node tempNode = node.getRightNode(); node = null; return tempNode; } else if (node.getRightNode() == null) { System.out.println(“Removing the left child node”); Node tempNode = node.getLeftNode(); node = null; return tempNode; } // 3. 삭제할 노드가 두개의 자식노드를 가진 경우 System.out.println(“Removing item with the children”); Node tempNode = getPredecessor(node.getLeftNode()); // 왼쪽 자식노드 중에서 가장 큰 노드 node.setData(tempNode.getData()); // 삭제할 노드의 데이터와 가장 큰 노드의 데이터 교환 node.setLeftNode(delete(node.getLeftNode(), tempNode.getData())); // 삭제 재귀호출 } // 높이 갱신 node.setHeight(Math.max(height(node.getLeftNode()), height(node.getRightNode())) + 1); // 삭제 완료후 트리 불균형 체크, 회전 수행 return settleDeletion(node); } // 회전 수행 : 삭제시 private Node
settleDeletion(Node node) { int balance = getBalance(node); // Left-Left or Left-Right 인 경우 if (balance > 1) { // Left-Right if (getBalance(node.getLeftNode()) < 0) { node.setLeftNode(leftRotation(node.getLeftNode())); // 왼쪽 회전 } return rightRotation(node); // 오른쪽 회전 } // Right-Right or Right-Left 인 경우 if (balance < -1) { // Right-Left if (getBalance(node.getRightNode()) > 0) { node.setRightNode(rightRotation(node.getRightNode())); // 오른쪽 회전 } return leftRotation(node); // 왼쪽 회전 } return node; } 3.6 테스트
// 테스트 클래스 public class App { public static void main(String[] args) { Tree
avlTree = new AVLTree<>(); avlTree.insert(“A”); avlTree.insert(“B”); avlTree.insert(“C”); avlTree.insert(“D”); avlTree.insert(“E”); avlTree.traverse(); avlTree.delete(“D”); avlTree.traverse(); } }
AVL 트리(AVL Tree)
이전 포스팅에서 완전 이진 탐색 트리에 대해 설명했지만 이 포스팅을 통해 들어온 사람들을 위해 다시 설명한 후 AVL트리를 설명하겠다.
완전 이진 탐색 트리(Complete Binary Search Tree)
이진 탐색 트리는 자료를 탐색하는데 최적화된 트리이다. 하지만 자료가 오름차순이나 내림차순과 같은 순서대로 입력되면 트리가 한쪽으로만 만들어지는 형태가 된다. 이는 연결 리스트와 다를 바 없는 형태로 이진 탐색 트리의 장점인 빠른 검색 속도를 이용하지 못한다. 이를 해결하기 위해 나온 것이 완전 이진 탐색 트리이다.
완전 이진 탐색 트리(Complete binary search tree)는 완전 이진 트리의 성질을 가지는 이진 탐색 트리이다. 완전 이진 탐색 트리는 편향된 이진 탐색 트리와는 다르게 항상 O(log n)의 검색 속도를 보장한다. 그러나 완전 이진 탐색 트리의 단점은 트리에 자료가 삽입될 때마다 완전 이진 탐색 트리의 형태를 유지하기 위해 트리의 모양을 바꾸어야 한다. 즉, 삽입할 때 많은 시간이 소요된다는 것이다. 삽입이 적고 탐색이 많은 경우에는 유리할 수 있으나 삽입하는 빈도수가 높아지면 높아질수록 효율성이 떨어진다. 이런 불편함을 해결한 것이 AVL트리이다.
AVL 트리란?(What is an AVL tree?)
AVL트리(AVL tree)는 1962년 아델슨 벨스키(Adelson-Velskii)와 랜디스(Landis)에 의해 제안된 트리로서, 트리 내의 모든 노드에 대해 왼쪽 서브 트리의 높이와 오른쪽 서브 트리의 높이가 1 이상 차이 나지 않는 높이 균형 이진 트리(height balanced binary tree)를 말한다. 아델슨 벨스키와 랜디스의 이름의 앞 글자를 따서 AVL트리라고도 부른다.
AVL트리의 검색 속도는 O(log n)의 탐색 시간을 가지며, 완전 이진 탐색 트리에 비하면 검색 시간이 더 걸리지만 유의미할 정도의 차이가 나지 않는다. 삽입 삭제 연산에 걸리는 시간 또한 검색 시간에 의해 좌우되기 때문에 삽입 삭제 연산 시간도 O(log n)의 시간 복잡도를 갖는다.
AVL트리에서 중요한 것은 균형 인수인데 균형 인수는 왼쪽 서브트리의 높이와 오른쪽 서브 트리의 차를 말한다. 균형 인수는 BF(T)로 나타내며 이는 트리 T의 양쪽 서브 트리에 대한 높이의 차이며, BF(T) = h(left tree) – h(right tree)로 나타낼 수 있다. 여기서 h는 트리의 높이를 말한다.
정의
이진 탐색 트리 내의 임의의 노드 N에 대해서 균형 인수 BF(N)가 -1, 0, 또는 1만의 값을 갖는다면 이 이진 탐색 트리를 AVL트리라 한다.
AVL 트리의 회전 연산(AVL tree rotation operation)
AVL트리에서의 삽입, 삭제 과정은 이진 탐색 트리에서의 삽입, 삭제 과정과 같다. 여기서 추가적으로 AVL트리에서는 삽입, 삭제후 균형 인수에 따라 트리를 재조정하는 과정이 추가된다. AVL트리에서 트리를 재조정하는 경우는 균형 인수의 절댓값이 2를 넘어가는 경우이다. 다시말해서 균형인수가 -2이하이거나 2이상인 경우 트리에 적절한 조절을 통해 균형 인수를 -1이상에서 1이하로 맞추어 준다. 트리의 재조정은 회전 연산을 통해 이루어진다. 다음 그림은 트리의 재조정이 필요한 경우이다.
회전에 대한 대략적인 내용을 표로 나타내면 다음과 같다.
회전을 하는 노드의 기준은 삽입의 경우는 삽입된 노드를 기준으로 균형인수가 깨진 가장 조상노드이고 삭제의 경우, 삭제하기 전 삭제로 인해 균형인수가 깨지는 가장 가까운 조상 노드이다.
LL회전(LL rotation or Single Right Rotation)
LL회전(LL rotation)은 왼쪽 서브 트리의 왼쪽 서브 트리에 노드가 추가되면서 불균형이 발생했을 떄 사용한다. 아래 그림을 보면 왼쪽 A 노드가 삽입되면서 C 노드의 균형인수가 깨졌다. 이를 해결하기위해 LL회전을 하여 다시 균형인수를 맞춘게 된다.
RR회전(RR rotation or Single Left Rotation)
RR회전(RR rotation)은 오른쪽 서브 트리의 오른쪽 서브 트리에 노드가 추가되면서 불균형이 발생했을 때 사용한다. 아래 그림을 보면 오른쪽 G 노드가 삽입되면서 D 노드의 균형인수가 깨졌다. 이를 해결하기위해 RR회전을 하여 다시 균형인수를 맞춘게 된다.
LR회전(LR rotation or Double(Left Right) Rotation)
LR회전(LR rotation)은 오른쪽 서브 트리의 왼쪽 서브 트리에 노드가 추가되면서 불균형이 발생했을 때 사용한다. 아래 그림을 보면 L 노드가 삽입되면서 G 노드의 균형인수가 깨졌다. 이를 해결하기위해 LR회전을 하여 다시 균형인수를 맞춘게 된다. LL회전과 RR회전을 순서대로 하면 LR회전이 된다.
RL회전(RL rotation or Double(Right Left) Rotation)
RL회전(RL rotation)은 왼쪽 서브 트리의 오른쪽 서브 트리에 노드가 추가되면서 불균형이 발생했을 때 사용한다. 아래 그림을 보면 B 노드가 삽입되면서 G 노드의 균형인수가 깨졌다. 이를 해결하기위해 RL회전을 하여 다시 균형인수를 맞춘게 된다. RR회전과 LL회전을 순서대로 하면 RL회전이 된다.
AVL트리의 구현(AVL tree implementation)
AVL 트리(높이 균형 이진 탐색 트리) 개념과 삽입 연산
728×90
이진 트리, 이진 탐색 트리 개념 참고
: hyunah-home.tistory.com/entry/%EC%9D%B4%EC%A7%84%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%84%B1%EC%A7%88-%EC%9A%B4%ED%96%89%EA%B3%BC-%EC%9D%91%EC%9A%A9-%EC%88%98%EC%8B%9D%ED%91%9C%ED%98%84-%ED%8A%B8%EB%A6%AC-%EC%9D%B4%EC%A7%84%ED%8A%B8%EB%A6%AC%EB%A1%9C%EC%9D%98-%EB%B3%80%ED%99%98%EB%B2%95-%EC%9D%B4%EC%A7%84%ED%83%90%EC%83%89%ED%8A%B8%EB%A6%AC
이진 탐색 트리
: key(왼쪽 부분 트리) < key(루트) < key(오른쪽 부분 트리)를 만족하는 이진 트리. 중순위 운행하면 오름차순으로 정렬된 값을 얻을 수 있다. AVL 트리(높이 균형 이진 탐색 트리) : 모든 노드의 왼쪽과 오른쪽 부분트리의 높이 차이가 1 이하인 이진탐색 트리. 삽입, 삭제 연산을 하여 트리 높이가 불균형 상태가 된다면 노드들을 재배치하여 높이 균형 상태로 만들어야 한다. 이진트리의 원소가 한쪽으로 몰려 연산의 속도가 늦어지는 단점을 보완할 수 있다. 균형 인수 = ( 왼쪽 부분 트리의 높이 - 오른쪽 부분트리의 높이 ) 즉, 높이 균형 이진 탐색 트리는 모든 노드의 균형인수의 절댓값이 1 이하여야 함. 높이 균형 이진 탐색트리의 예시와 각 노드별 균형 인수 AVL 트리 노드의 구현 코드는 다음과 같다. typedef struct Avl_node{ struct Avl_node *left_child, *right_child; int data; } Avl_node; Avl_node *root; 728x90 AVL 트리의 연산 탐색 연산 : 이진 탐색 트리와 동일. 노드 구성의 변화가 일어나는 연산이 아니므로 ( 노드가 새로 생기거나 삭제되는 것이 아님 ) 높이 균형을 유지하기 위한 별도의 균형 조정 연산이 필요하지 않다. 삽입 연산 : 삽입 후에 불균형 상태가 되었다면, 불균형 상태가 된 ( 균형 인수의 절댓값이 2 이상이 된 ) 노드 중에서, 삽입 위치에서 가장 가까운 조상 노드(A)의 부분트리를 조정하는 재균형 연산이 별도로 필요하다. 재균형 연산은 A를 중심으로 회전함으로써 진행한다. 예시를 보면서 더 자세히 설명해보겠다. 삽입 시행 전 삽입 연산을 시행하기 전에 트리 구성이 위의 그림과 같았다고 하자. 저 트리에서 1이라는 값을 가진 노드를 새로 삽입하면, 3의 왼쪽 부분 트리에 위치하여, 5와 7의 균형 인수값을 초과되게 만든다. 따라서 삽입 위치에서 가장 가까운 조상 노드인 5를 기준으로 회전(rotation)을 시행해야 한다. 삽입 시행 후 5를 기준으로 오른쪽으로 회전시킨 결과 3이 5의 부모노드가 되고, 모든 노드의 균형 인수 값이 1 이하인 상태로 변화하였다. 이러한 회전 연산을 재균형 연산이라고 하며, 회전의 종류에는 총 4가지가 존재한다. 삽입된 노드(위의 예시에서는 1)를 N이라고 하고, 삽입 노드로부터 가장 가까우면서 균형 인수가 초과된 조상 노드(위의 예시에서는 5)를 A라고 하겠다. LL 회전 예시 - LL 회전 (단회전) : N이 A의 왼쪽 자식 노드의 왼쪽 부분트리에 삽입된 경우에 시행. 위에서 소개한 예시 역시 이 경우에 해당한다. A부터 N까지의 노드를 오른쪽으로 회전시킨다. 위 그림에서 N은 2, A는 6이다. 6을 기준으로 2부터 6까지의 노드를 오른쪽으로 회전시켰다. 자식노드였던 5가 부모노드였던 6을 오른쪽 자식으로 가지는 것이다. 이때 5가 가지고 있던 기존의 오른쪽 부분트리는 부모노드였던 6의 왼쪽 부분트리로 이동한다. 그림으로 표시하면 이렇게 된다. 위 그림에서의 5가 이 그림에서 B에 해당하고, 균형 인수가 초과한 노드인 6이 A에 해당한다. 자식노드였던 B가 부모노드였던 A를 오른쪽 자식으로 가지게 되고, B의 오른쪽 부분트리(파란색)은 부모노드였던 A의 왼쪽 부분트리로 이동하였다. 이러한 과정을 코드로 나타내면 다음과 같다. Avl_node *rotate_LL(Avl_node *parent){ Avl_node *child = parent->left_child; parent->left_child = child->right_child; child->right_child = parent; return child; }
RR 회전 예시
– RR 회전 (단회전)
: N이 A의 오른쪽 자식 노드의 오른쪽 부분트리에 삽입된 경우에 시행. LL 회전의 정반대 경우로, A부터 N까지의 노드를 왼쪽으로 회전시킨다. 위 그림에서 N은 9, A는 6이며 6을 기준으로 9부터 6까지의 노드를 왼쪽으로 회전시켰다. 자식 노드였던 8이 부모노드였던 6을 왼쪽 자식으로 가지게 된다. 8이 가지고 있던 기존의 왼쪽 부분트리는 부모노드였던 6의 오른쪽 부분트리로 이동한다.
그림으로 표시하면 이렇게 된다. 위 그림에서의 8가 이 그림에서 B에 해당하고, 균형 인수가 초과한 노드인 6이 A에 해당한다. 자식노드였던 B가 부모노드였던 A를 왼쪽 자식으로 가지게 되고, B의 왼쪽 부분트리(파란색)는 부모노드였던 A의 오른쪽 부분트리로 이동하였다.
코드로 표현하면 이렇다.
Avl_node *rotate_RR(Avl_node *parent) { Avl_node *child = parent->right_child; parent->right_child = child->left_child; child->left_child = parent; return child; }
다음으로 소개하는 회전은 두 번의 회전이 이루어지는 복회전이다. 그러나 그 사실보다 중요한 것은 결과적인 A와 N의 위치관계이다. 따라서 과정이 이해가 가지 않는다면, 그림을 보고 A,B,C의 위치 관계가 어떻게 변화하는지를 중점으로 이해하려고 노력하길 권한다.
LR 회전 예시
– LR 회전 (복회전)
: N이 A의 왼쪽 자식 노드의 오른쪽 부분트리에 삽입된 경우에 시행. 왼쪽으로 한 번 회전(RR 회전)한 후, 오른쪽으로 한 번 회전(LL회전)하여 N이 A의 자식 노드와 A 노드 사이에 위치하도록 만든다. 위 그림에서 N은 파란색으로 표시된 C의 왼쪽 부분트리에 속한 노드이고, A는 A이다. B를 기준으로 RR 회전하여 A의 왼쪽 자식노드가 C가 되도록 하고, C가 왼쪽 자식노드로 B를 가지도록 만든 후, A를 기준으로 LL 회전하여 C가 B와 A의 중간에 위치하는 부모노드가 되도록 만든다. 기존 C의 왼쪽 부분트리와 오른쪽 부분트리는 각각 B의 오른쪽 부분트리와 A의 왼쪽 부분트리로 이동한다.
이러한 과정을 코드로 표현하면 다음과 같다.
Avl_node *rotate_LR(Avl_node *parent){ Avl_node *child = parent->left_child; parent-> left_child = rotate_RR(child); return rotate_LL(parent); }
RL 회전 예시
– RL 회전 (복회전)
: N이 A의 오른쪽 자식 노드의 왼쪽 부분트리에 삽입된 경우에 시행. 오른쪽으로 한 번 회전(LL회전)한 후, 왼쪽으로 한 번 회전(RR회전)하여 N이 A와 A의 자식 노드 사이에 위치하도록 만든다. 위 그림에서 N은 파란색으로 표시된 C의 왼쪽 부분트리에 속한 노드이고 A는 A이다. B를 기준으로 LL 회전하여 A의 오른쪽 자식노드가 C가 되도록 하고, C가 오른쪽 자식노드로 B를 가지도록 만든 후, A를 기준으로 RR회전하여 C가 A와 B의 중간에 위치하는 부모노드가 되도록 만든다.
C가 가지고 있던 기존의 왼쪽, 오른쪽 부분트리는 각각 A의 오른쪽 부분트리와 B의 왼쪽 부분트리로 이동한다.
이 과정을 코드로 표현하면 다음과 같다.
Avl_node *rotate_RL(Avl_node *parent){ Avl_node *child = parent->right_child; parent->right_child = rotate_LL(child); return rotate_RR(parent); }
이러한 재균형 연산을 사용하는 AVL 트리 삽입 연산의 전체 코드는 아래와 같다.
// 균형 인수 구하기 int get_height_diff(Avl_node *node){ if (node == NULL) return 0; return get_height(node->left) – get_height(node->right); // 삽입 후 재균형 Avl_node *rebalance(Avl_node **node){ int height_diff = get_height_diff(*node); if (height_diff > 1){ if (get_height_diff((*node)->left_child) > 0) *node = rotate_LL(*node); else *node = rotate_LR(*node); } else if (height_diff < -1){ if (get_height_diff((*node)->right_child) < 0) *node = rotate_RR(*node); else *node = rotate_RL(*node); } return *node; } void insert_node_Avl(Avl_node **root, int key){ insert_node(root, key); rebalance(root); } 시간 복잡도 : 높이가 h인 AVL 트리에서 삽입연산을 시행할 때, 최대 1번의 회전이 필요하다. 따라서 삽입 연산의 시간복잡도는 일반 이진트리에서의 삽입 연산 시간 복잡도와 동일한 O(h)(=O(logn))이다. 728x90
9. AVL 트리
AVL 트리는 모든 내부노드 $v$에 대해, $v$의 좌우 자식들의 높이 차이가 1을 넘지 않는 이진 탐색 트리이다.
AVL 트리의 부트리 역시 AVL 트리이며, 높이 정보는 각 내부 노드에 저장된다.
AVL 트리의 높이균형 속성 덕분에, $n$개의 원소를 저장하는 AVL 트리의 높이는 $O(\log n)$이 보장된다. (이진 탐색 트리는 최악의 경우 $O(n)$)
AVL 트리에서의 삽입/삭제
AVL 트리에서 원소의 삭제와 삽입은 이진 탐색 트리와 유사하나, 삽입 혹은 삭제로 인해 AVL 트리의 높이균형 속성이 파괴될 수 있으므로 이를 복원해주는 작업이 추가된다.
삽입
def insertItem(k, e): w = treeSearch(root(), k) if isInternal(w): raise Exception(“Key Already Exists!”) w.elem = e w.key = k expandExternal(w) searchAndFixAfterInsertion(w) # 이 부분이 추가된다. return
def searchAndFixAfterInsertion(w): z = w while !isRoot(z): z = w.parent() # 루트까지 거슬러 올라가다가 if !rightBalance(z): # 처음 만난 밸런스 깨진 노드가 z가 된다. break if isRoot(z) and rightBalance(z): # 만약 루트까지 전부 정상이면 리턴 return y = higherChild(z) # y는 z의 높은 자식 x = higherChild(y) # x는 y의 높은 자식 restructure(x, y, z) # 균형을 복구한다. 하위 트리의 균형이 복구되면 루트까지 모든 균형이 복구된다. return
def reconstructure(x,y,z): a,b,c,t0,t1,t2,t3 = inorder(x,y,z) # x,y,z의 중위순회 순서와, x,y,z를 제외한 나머지 4개의 부트리 swap(z, b) # 루트를 b로 바꾸고 a.left = t0 a.right = t1 c.left = t2 c.right =t3 b.left = a b.right = c return b
삭제
def removeElement(k): w = treeSearch(root(), k) if isExternal(w): raise Exception(“No Such Key!”) e = w.elem z = leftChild(w) if !isExternal(z): z = rightChild(w) if isExternal(z): # 자식 중 외부노드 존재 zs = reduceExternal(z) # 그냥 지우기 else: y = inOrderSucc(w) # 중위순회 후계자 탐색 z = leftChild(y) w.key = y.key w.elem = y.elem zs = reduceExternal(z) searchAndFixAfterRemoval(zs) # 균형 복구 return e
def searchAndFixAfterRemoval(w): z = w while !isRoot(z): z = w.parent() # 루트까지 거슬러 올라가다가 if !rightBalance(z): # 처음 만난 밸런스 깨진 노드가 z가 된다. break if isRoot(z) and rightBalance(z): # 만약 루트까지 전부 정상이면 리턴 return y = higherChild(z) if (y.left.height == y.right.height): # y 의 두 자식의 높이가 같으면 if isLeft(y): # y랑 같은 쪽을 자식으로 x = y.left else: x = y.right else: x = higherChild(y) b = reconstructure(x,y,z) if isRoot(w): return else: searchAndFixAfterRemoval(w.parent()) #루트까지 재귀 반복
AVL 트리의 성능
AVL 트리를 이용하여 구현된 원소 $n$개의 사전은 $O(n)$ 공간과 $O(\log n)$ 높이를 가진다.
3 노드를 개조하는 reconstructure 작업은 $O(1)$ 시간
findElement는 $O(\log n)$ 시간
insertItem, removeElement는 $O(\log n)$ 시간이 소요된다. 초기의 treeSearch에 $O(\log n)$ 균형 복구에 $O(\log n)$
AVL Tree
728×90
반응형
Goal
자가 균형 트리에 대한 이해
AVL 트리에 대한 이해
AVL 트리를 직접 구현해본다.
AVL Tree란?
자가 균형 이진탐색 트리(self-balancing binary search tree) 일종으로,
왼쪽 서브트리의 높이와 오른쪽 서브트리의 높이 차이가 1이하인 이진 탐색 트리를 말한다.
AVL 트리는, 트리가 비균형 상태가 되면 스스로 노드들을 재배치(self-balancing)하여 균형 상태로 만든다.
따라서, 항상 균형 트리를 보장하기 때문에 O(logn)의 탐색 시간을 보장한다.
*이진 탐색 트리에서 균형을 유지하는 것이 중요한 이유
더보기 이진 탐색 트리 연산(삽입, 삭제, 탐색)의 시간 복잡도는 탐색 시간에 지배된다. 그렇기 때문에 이진 탐색 트리에서 균형을 유지하는 것은 굉장히 중요하다. 이진 탐색 트리의 균형이 유지될 경우 탐색 시간은 O(logn)의 탐색 시간을 보장한다.
AVL-Tree의 시간 복잡도
AVL의 성질
Balanced Factor = {-1, 0 , 1}
*Balanced Factor(BF) := Height(left subtree) – Height(right subtree)
즉, Balanced Factor는 왼쪽 서브 트리와 오른쪽 서브 트리의 높이 차로 정의된다. (BF 차가 클수록 불균형 이진 트리)
AVL트리에서는 높이 차가 항상 1이하의 값만 유지되기 때문에 Balanced Factor는 -1, 0, 1 값 중 하나이다.
left-heavy : Height(left subtree) > Height(right subtree)
right-heavy : Height(left subtree) < Height(right subtree) balanced : Height(left subtree) = Height(right subtree) 위와 같은 성질 때문에 AVL 트리는 O(logn)의 연산 시간을 갖게 된다. (높이 h = logn) AVL 트리가 균형을 유지하는 방법 AVL 트리는 삽입, 삭제 연산시 균형이 깨질 수 있는데, 이때 회전(rotation) 연산을 통해 트리를 재구성하여 AVL 트리의 균형 성질을 유지시킨다. 삽입, 삭제 연산시 불균형 상태(BF가 2 혹은 -2)로 바뀐 노드를 기준으로 그 서브트리들의 위치를 rotation하는 방식을 취한다. rotation에는 두 가지 방식이 있는데 삽입 연산을 중심으로 살펴 보도록 하자. AVL 트리에서 균형이 깨지는 경우 삽입 연산 : 항상 단말 노드의 왼쪽 자식, 오른쪽 자식에서 이루어 진다. 이때, 삽입 노드로부터 가장 가까우면서 BF가 +2 또는 -2인 조상 노드의 서브 트리를 회전 시켜 트리의 균형을 맞춘다. 시켜 트리의 균형을 맞춘다. 삭제 연산 : 삭제 연산 이후, 노드가 불균형 상태가 되면 마찬가지로 회전 연산을 통해 트리의 균형을 맞춘다. 삽입 연산 가장 가까운 조상 노드를 A, 새로 삽입된 노드를 N이라 할 때, A의 BF가 불균형 상태가 되는 경우는 다음과 같이 4가지 경우로 나뉜다. (조상 노드가 있을 경우에만 불균형 상태 발생) LL 타입 : N이 A의 왼쪽 서브트리의 왼쪽 서브트리에 삽입된다. LR 타입 : N이 A의 왼쪽 서브트리의 오른쪽 서브트리에 삽입된다. RR 타입 : N이 A의 오른쪽 서브트리의 오른쪽 서브트리에 삽입된다. RL 타입 : N이 A의 오른쪽 서브트리의 왼쪽 서브트리에 삽입된다. 이떄, LL, RR은 서로 대칭, LR, RL 여깃 서로 대칭이다. 다음은 균형을 맞추기 위한 회전 연산을 나타낸다. LL 회전 : A부터 N까지의 경로상의 노드들을 오른쪽으로 회전 시킨다. 시킨다. LR 회전 : A부터 N까지의 경로상의 노드들을 왼쪽, 오른쪽 회전 시킨다. 시킨다. RR 회전 : A부터 N까지의 경로상의 노드들을 왼쪽으로 회전 시킨다. 시킨다. RL 회전 : A부터 N까지의 경로상의 노드들을 오른쪽, 왼쪽 회전 시킨다. 한번만 회전 시키는 경우를 single rotation, 두번의 회전을 하는 경우 double rotation이라고 하는데, LL, RR 회전은 single rotation에 해당하고 LR, RL 회전은 double rotation에 해당한다. single right rotation : 오른쪽으로 한번 회전 (= LL회전) single left rotation : 왼쪽으로 한번 회전 (= RR 회전) double rotation(left-right) : 왼쪽 회전을 한 후 오른쪽 회전 (= LR 회전) double rotation(right-left) : 오른쪽 회전을 한 후 왼쪽 회전 (= RL 회전) 참고) 더보기 삽입하는 노드의 조상 노드를 A, 부모 노드를 P라 할 때, 다음과 같은 특징을 갖는다. single right rotation : P는 A의 왼쪽 자식 노드이며, P는 right-heavy가 아니다. (0<= BF(P)) single left rotation : P는 A의 오른쪽 자식 노드이며, P는 left-heavy가 아니다. (BF(P) <= 0) double rotation : P는 A의 왼쪽 ( (BF(P) = -1) single rotation Z를 잡아 당겨 V를 새로운 루트 노드로 만든다. U의 왼쪽 서브 트리를 V의 오른쪽 서브 트리로 변경 V의 오른쪽 서브 트리를 U로 변경 U, V의 Balanced Factor 값 변경 (0, 0) 위 과정을 거치면 다음과 같은 형태가 된다. single rotation연산을 일반화 하면 다음과 같은 형태로 나타낼 수 있다. U : BF의 절대값이 2 이상이면서 새 노드와 가장 가까운 조상 노드 V : U의 자식노드, BF 절대값이 1이하 single rotation은 다음 두 가지 경우에 V를 중심으로 실시한다. V가 U의 왼쪽 자식노드, V의 왼쪽 서브트리에 새 노드 삽입 : V를 기준으로 right rotation V가 U의 오른쪽 자식노드, V의 오른쪽 서브트리에 새 노드 삽입 : V를 기준으로 left rotation Left/Right rotation을 직관적으로 나타낸 경우 AVL 트리 삽입 연산 동작 과정 double rotation U : BF의 절대값이 2 이상이면서 새 노드와 가장 가까운 조상 노드 V : U의 자식노드, BF 절대값이 1이하 W : V의 자식 노드, 회전의 중심이 BF 절대값이 1이하 double rotation은 다음 두 가지 경우에 실행된다. V가 U의 왼쪽 자식노드, V의 오른쪽 서브트리에 새 노드 삽입 V가 U의 오른쪽 자식노드, V의 왼쪽 서브트리에 새 노드 삽입 다음 그림은 double rotation을 일반화 한 그림이다. (left-right rotation 형태의 그림이지만 right-left도 동일) W를 중심으로 first rotation 진행 (위 그림에서는 left rotation. A를 잡아 당김) 다시 W를 중심으로 second rotation 진행 (위 그림에서는 right rotation. D를 잡아 당김) 삭제 연산 삭제 연산도 삽입 연산과 마찬가지로, Balanced Factor가 불균형 상태가 된 것이 있다면, rebalance과정을 거쳐 트리를 균형상태로 변경하면 된다. 구현 코드 References https://gist.github.com/Harish-R/097688ac7f48bcbadfa5#file-avl-tree-cpp-L85 https://ratsgo.github.io/data%20structure&algorithm/2017/10/27/avltree/ https://doublesprogramming.tistory.com/237 728x90 반응형
키워드에 대한 정보 avl 트리 삭제
다음은 Bing에서 avl 트리 삭제 주제에 대한 검색 결과입니다. 필요한 경우 더 읽을 수 있습니다.
이 기사는 인터넷의 다양한 출처에서 편집되었습니다. 이 기사가 유용했기를 바랍니다. 이 기사가 유용하다고 생각되면 공유하십시오. 매우 감사합니다!
사람들이 주제에 대해 자주 검색하는 키워드 [코드라떼] 자바 자료구조 – AVL 트리 개념
- 동영상
- 공유
- 카메라폰
- 동영상폰
- 무료
- 올리기
YouTube에서 avl 트리 삭제 주제의 다른 동영상 보기
주제에 대한 기사를 시청해 주셔서 감사합니다 [코드라떼] 자바 자료구조 – AVL 트리 개념 | avl 트리 삭제, 이 기사가 유용하다고 생각되면 공유하십시오, 매우 감사합니다.