Tree
Table of contents
트리
정의
- 데이터 사이의 계층 관계를 나타내는 자료구조
특징
- 노드가 N개인 트리는 항상 N-1 개의 간선(edge) 를 가진다.
- 루트에서 어떤 노드로 가는 경로는 유일하다.
- 루트 노드는 단 1개 뿐이다.
- 모든 자식 노드는 한 개의 부모 노드만을 가진다.
용어
-
루트(root)
- 트리의 가장 윗부분에 위치하는 노드
- 하나의 트리에는 하나의 루트 노드만 존재
-
리프(leaf)
- 트리의 가장 아랫부분에 위치하는 노드(더 이상 뻗어나갈 수 없는)
-
자식(child)
- 어떤 노드로부터 아래로 연결된 노드
- 노드는 여러 개의 자식 노드를 가질 수 있다.
-
부모(parent)
- 어떤 노드로부터 위로 연결된 노드
- 노드는 단 1개의 부모 노드를 가진다.
-
형제(sibling)
- 같은 부모를 가지는 노드
-
조상(ancestor)
- 어떤 노드에 위쪽으로 연결된 모든 노드
-
자손(descendant)
- 어떤 노드에 아래쪽으로 연결된 모든 노드
-
레벨(level)
- 루트로부터 얼마나 떨어져 있는 지에 대한 값
- 루트의 레벨은 0 , 하나씩 뻗어나갈 때 마다 1씩 증가
-
차수(degree)
- 노드가 갖는 자식의 수
- 모든 노드의 차수가 n이하인 트리를 n진 트리라고 정의한다.
- ex) 2진 트리 -> 차수가 2 이하인 트리
LCRS 트리 (Left Child, Right Siblings)
정의
- 왼쪽에는 자식이 붙고, 오른쪽에는 형제 노드만 붙는 형태의 트리
특징
- 노드의 데이터 & 왼쪽 자식에 대한 포인터 & 오른쪽 형제에 대한 포인터만 필요
이진 트리
정의
- 이진트리는 각 노드가 최대 두 개의 자식을 갖는 트리를 뜻한다.
- 각 노드는 자식이 ( 0 ~ 2 ) 개만을 갖는 것이다.
포화 이진 트리
정의
- 모든 레벨이 노드로 꽉 차 있는 트리를 뜻한다.
특징
- 완전 이진트리의 성질도 만족해야 한다.
- 모든 말단 노드가 동일한 깊이 또는 레벨을 갖는다.
- k를 트리의 높이라 하면, 트리의 노드 개수가 정확히 2^k개여야 한다.
</br>
완전 이진 트리
특징
- 마지막 레벨을 제외하고 모든 레벨이 완전히 채워져 있으며, 마지막 레벨의 노드는 왼쪽에서 오른쪽으로 채워져야 한다.
- 마지막 레벨 h에서 1부터 2^h-1 개의 노드를 가질 수 있다.
</br>
경사트리
- 한 쪽으로 치우친 트리
- 경사 이진 트리일 경우 노드 수를 n개라고 할 때 트리의 높이가 n과 같게 된다.
- 시간복잡도 O(n) (이진 트리 중 효율 최악)
- 이진 탐색 트리의 효율을 높이기 위해서는 가능한 트리를 좌우로 균형있게 설계해야 한다.
</br>
트리 순회
-
전위 순회(Preorder Traversal)
- 루트 노드를 시작으로 왼쪽 서브 트리 -> 오른쪽 서브 트리 순으로 순회
- 현재 노드 -> 노드 방문 > 오른쪽 자식
- 예시 : A-B-D-H-E-I-J-C-F-G-K
-
중위 순회(Inorder Traversal)
- 왼쪽 최하위 노드를 시작으로 왼쪽 서브 트리 -> 루트 노드 -> 오른쪽 서브 트리 순으로 순회
- 왼쪽 자식 -> 노드 방문 -> 오른쪽 자식
- 예시 : H-D-B-I-E-J-A-F-C-G-K
-
후위 순회(Postorder Traversal)
- 왼쪽 최하위 노드를 시작으로 왼쪽 서브트리 -> 오른쪽 서브트리 -> 루트 노드 순으로 순회
- 왼쪽 자식 -> 오른쪽 자식 -> 노드 방문
- 예시 : H-D-I-J-E-B-F-K-G-C-A
-
[참고] 이진 트리의 level order traversal
- 이진 트리 순회 방법으로, 트리의 레벨 순서대로 순회하는 방법이다.
- BFS 알고리즘과 동일하다고 봐도 된다. (단지 이진 트리에서 사용할 뿐)
- 루트 노드 -> 루트 노드의 Left Child -> 루트 노드의 Right Child </br>
수식트리
정의
- 이진 트리의 일종으로 수식을 트리 형태로 만든것
특징
- 하나의 연산자가 두 개의 피연산자를 취한다는 가정
- 피연산자는 잎 노드
- 연산자는 루트 노드 or 가지 노드
원리
-
컴퓨터는 중위 표기식을 처리하기 적합하지 않기에 입력받은 수식을 후위 표기식으로 변환 후 트리를 구성한다
- ex) ( 1 + 2 ) _ 7 => 1 2 + 7 _
- 스택을 사용
-
후위 표기법의 수식을 앞에서부터 차례대로 참조
- 피연산자를 만나면 무조건 스택으로 옮긴다.
- 연산자를 만나면 스택에서 두 개의 피연산자를 꺼내어 자식 노드로 연결한다.
- 자식 노드로 연결해서 만들어진 트리는 다시 스택으로 옮긴다.
장점
- 순회를 통해 다양한 표기법으로 수식 표현 가능
</br>
AVL 트리
정의
- 스스로 균형을 잡는 데이터 구조
- 자가 균형 이진 탐색 트리
특징
- 이진 탐색 트리의 속성을 가진다.
- 왼쪽, 오른쪽 서브 트리의 높이 차이가 최대 1이다.
- 높이 차이가 1보다 커지면 회전을 통해 균형을 맞춘다.
원리
용어
- 균형 인수 (Balance Factor (BF))
- 왼쪽 서브트리의 높이 - 오른쪽 서브트리의 높이
-
회전 (Rotation)
- 노드 삽입이나 삭제 시 불균형 상태 (BF != -1,0,1) 인 경우 불균형 노드를 기준으로 서브트리의 위치 변경
- 불균형 Case
- LL (Left Left)
- RR (Right Right)
- LR (Left Right)
- RL (Right Left)
LL (Left Left)
- Right Rotation 수행
- y 노드의 오른쪽 자식 노드를 z 노드로 변경
- z 노드 왼쪽 자식 노드를 y 노드 오른쪽 서브트리(T2)로 변경
- y가 루트 노드
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 (Right Right)
- Left Rotation 수행
- y 노드의 왼쪽 자식 노드를 z 노드로 변경
- z 노드의 오른쪽 자식 노드를 y 노드 왼쪽 서브트리(T2)로 변경
- y가 루트 노드
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 (Left Right)
- Left Rotation 수행
- Right Rotation 수행
y = z->left;
y = leftRotate(y);
z = rightRotate(z);
RL (Right Left)
- Right Rotation 수행
- Left Rotation 수행
y = z->right;
y = rightRotate(y);
z = leftRotate(z);
시간복잡도
- O(logn) (n : 노드의 개수)
분리 집합
정의
- 교집합이 존재하지 않는 2개 이상의 집합
특징
- 구분해야 하는 데이터 집합들을 다루는 알고리즘에서 사용
- 보통 특정 집합의 대표 노드(루트)를 자식노드가 가리킴
원리
연산
- Find
-
- 노드 x가 어느 집합에 포함되어 있는지 찾는 메소드
- 한 노드에 대해 Find 연산을 할 때마다 그 노드의 부모노드를 항상 root로 만들어준다.
int find_root(int x) {
//x가 root이면, 그대로 반환
if (x == parent[x]) return x;
//x가 자식 노드일 경우, 부모 노드에 대해 재귀 실행
//parent[x]를 최종적으로 찾을 root 노드로 갱신
return parent[x] = find_root(parent[x]);
}
-
Union (합집합)
- 각 두 집합의 대표 노드(루트)를 찾고 한 집합의 대표노드의 부모를 다른 집합의 대표노드로 설정
void union_root(int x, int y) {
//x, y 정점의 최상위 정점을 각각 찾음 (속한 트리의 루트 노드를 찾음)
x = find_root(x);
y = find_root(y);
if (x != y) {
//서로 다른 트리에 속한다면, 한 쪽의 트리를 다른 쪽에 붙임
//항상 높이가 낮은 트리를 높이가 높은 트리에 붙임
//합친 높이가 낮아야 시간복잡도를 줄일 수 있음
if (node_height[x] < node_height[y]) parent[x] = y;
else parent[y] = x;
//만약 합친 두 트리의 높이가 같다면, 합친 후 트리의 높이를 1 증가
if (node_height[x] == node_height[y]) {
parent[x] = y;
node_rank[x]++;
}
}
}