[Date Structure] 트리
1. 트리의 개념
- 노드 : 간선과 함께 트리를 구성하는 것
- 루트 노드 : 계층적인 구조에서 가장 높은 곳에 있는 노드
- 서브트리: root node를 제외하는 나머지 node
- 간선(edge 에지) : tree에서 root와 subtree를 연결하는 선
- parent node
- child node
- sibling
- ancestor node : 임의의 노드 하위에서 루트 노드까지의 경로를 이루고 있는 노드들
- descendent node : 임의의 노드 하위에 연결된 모든 노드들, 즉 어떤 node의 subtree에 속하는 모든 node들
- terminal node : child node가 없는 node
- nonterminal node : child node가 있는 node
- Degree(차수) of node : 어떤 node가 가지고 있는 child node의 수
- Degree of tree : tree가 가지고 있는 node의 차수 중에서 가장 큰 값
- Level : tree 각 층의 번호
- Height : tree가 가지고 있는 최대 level
2. 이진 트리
- Binary tree : 공집합이거나, root, left subtree, and right subtree 구성된 node들의 유한 집합. 각 node가 최대 2개의 child node를 갖는 tree. 즉 각 node는 자식이 없거나 한 개 또는 두 개이다.
이진트리의 subtree는 모두 이진트리다.
이진트리의 성질
이진 트리의 노드의 개수가 n이고 높이가 h일 때,
이진 트리의 성질 | ||
---|---|---|
이진트리의 간선의 개수 | $n - 1$ | |
이진트리의 노드의 개수 | $h \leq$ 이진트리의 노드의 개수 $\leq 2^h - 1$ | 최소 : 한 레벨에 하나의 노드가 있을 때, 최대 : 포화 이진트리 |
이진트리의 높이 | $[\log_2(n + 1)] + 1 \leq$ 이진트리의 높이 $\leq n$ | |
트리의 null의 개수 | $n+1$ | |
leaf node의 개수 | 2개 child node를 가진 node의 개수 + 1 |
이진트리의 분류
이진 트리의 분류 | 정의 | 특성 |
---|---|---|
포화 이진트리(full binary tree) | 트리의 각 레벨에 노드가 꽉 차있는 이진 트리 | 모든 내부 노드가 두 개의 자식 노드를 가지고 있으며, 모든 리프 노드가 같은 레벨에 있는 이진 트리 |
완전 이진트리(complete binary tree) | 높이가 k인 트리에서 레벨 1부터 k-1까지는 노드가 모두 채워져 있고 마지막 레벨 k에서는 왼쪽부터 오른쪽으로 노드가 순서대로 채워져 있는 이진트리 | 마지막 레벨을 제외한 모든 레벨이 완전히 채워진 이진 트리 |
추상 자료형
데이터 : node의 집합. 공집합이거나, 루트노드와 왼쪽 서브트리, 오른쪽 서브트리로 구성됨. 모든 서브트리도 이진트리이어야 함.
연산 - init() : 이진트리를 초기화한다.
- is_empty() : 이진트리가 공백상태인지 확인한다.
- create_tree(e, left, right) : 이진트리 left와 right를 각각 왼족과 오른쪽 자식으로하고 e를 루트로 하는 이진트리를 생성한다.
- get_root() : 이진트리의 루트 노드를 반환한다.
- get_count() : 이진트리의 노드의 수를 반환한다.
- get_leaf_count() : 이진트리의 단말 노드의 수를 반환한다.
= get_height() : 이진트리의 높이를 반환한다.
3. 구현
이진트리도 선형 자료형에서와 마찬가지로 배열을 이용해 표현하거나 링크를 이용한 연결된 표현 방법으로 나타낼 수 있다.
배열 표현법
이진트리가 완전 이진트리라고 가정하여 트리의 높이(k)에 따라 알맞는 크기($2^k-1$)의 배열을 생성하고, 완전 이진트리의 번호대로 노드들을 저장한다.
포화 이진트리나 완전 이진트리에는 적합하지만 일반적인 이진트리에서는 기억 공간의 낭비가 심할 수 있다.
어떤 노드의 인덱스$(i)$를 알면 그 노드의 부모와 자식의 인덱스를 계산할 수 있다.
- Parent node index = i / 2
- Left child node index = 2i
- Right child node index = 2i + 1
링크 표현법
데이터를 저장하는 필드, 왼쪽 자식 노드를 가리키는 포인터 변수, 오른쪽 자식 노드를 가리키는 포인터 변수로 구성된 노드를 이용한다.
#include <stdio.h>
#include <stdlib.h>
typedef int TElement;
typedef struct BinTrNode {
TElement data; // 노드에 저장할 데이터
struct BinTrNode* left; // 왼쪽 자식 노드의 포인터
struct BinTrNode* right; // 오른쪽 자식 노드의 포인터
} TNode;
TNode* root; // root node의 포인터
이진 트리 초기화 함수
// 이진 트리 초기화하기
void init_tree() {root = NULL;}
이진 트리 공백 여부 확인 함수
// 공백 상태 확인하기
int is_empty_tree() {return root == NULL;}
이진 트리 생성 함수
// 이진트리 생성하기
TNode* create_tree(TElement val, TNode* l, TNode* r)
{
TNode* n = (TNode*)malloc(sizeof(TNode));
n -> data = val;
n -> left = l;
n -> right = r;
return n;
}
이진 트리의 루트 노드 반환 함수
// 루트 노드 반환하기
TNode* get_root() {return root;}
이진 트리의 노드 개수 함수
// 노드 개수 구하기
int count_node(TNode *n)
{
if(n == NULL) return 0;
return 1 + count_node(n -> left) + count_node(n -> right);
}
이진 트리의 단말 노드 개수 함수
// 단말 노드 개수 구하기
int count_leaf(TNode *n) {
if(n == NULL) return 0;
if(n -> left == NULL && n -> right == NULL) return 1;
else return count_leaf(n -> left) + count_leaf(n -> right);
}
이진 트리의 높이 함수
// 트리의 높이 구하기
int calc_height(TNode *n)
{
int hLeft, hRight;
if(n == NULL) return 0;
hLeft = calc_height(n -> left);
hRight = calc_height(n -> right);
return (hLeft > hRight) ? hLeft + 1 : hRight + 1;
}
int main()
{
TNode *a, *b, *c, *d, *e, *f, *g, *h, *i, *j;
init_tree();
g = create_tree(7, NULL, NULL);
h = create_tree(8, NULL, NULL);
i = create_tree(9, NULL, NULL);
j = create_tree(10, NULL, NULL);
f = create_tree(6, NULL, NULL);
d = create_tree(4, g, h);
e = create_tree(5, i, j);
b = create_tree(2, d, e);
c = create_tree(3, NULL, f);
a = create_tree(1, b, c);
printf("노드의 개수 = %d\n", count_node(a));
printf("단말의 개수 = %d\n", count_leaf(a));
printf("트리의 높이 = %d\n", calc_height(a));
return 0;
}
// 출력 결과
// 노드의 개수 = 10
// 단말의 개수 = 5
// 트리의 높이 = 4
4. 이진트리의 순회
- Traversal(순회) : 트리에 속하는 모든 노드를 한 번씩 방문하여 노드의 데이터를 목적에 맞게 처리하는 것
이진트리는 전체 트리와 서브 트리의 구조가 완전히 동일하다. 전체 트리 순회에 사용된 알고리즘을 서브 트리에 동일하게 적용할 수 있다.
- Preorder traversal(전위 순회) : 루트(자기 자신) -> 왼쪽 서브트리 -> 오른쪽 서브트리
- Inorder traversal(중위 순회) : 왼쪽 서브트리 -> 루트(자기 자신) -> 오른쪽 서브트리
- Postorder traversal(후위 순회) : 왼쪽 서브트리 -> 오른쪽 서브트리 -> 루트(자기 자신)
- Level order(레벨 순회) : 각 노드를 레벨 순으로 검사하는 방법
순회 방법 | 순회 결과 |
---|---|
전위 순회 | A-B-D-H-I-E-J-K-C-F-G |
중위 순회 | H-D-I-B-J-E-K-A-F-C-G |
후위 순회 | H-I-D-J-K-E-B-F-C-G-A |
레벨 순회 | A-B-C-D-E-F-G-H-I-J-K |
#include <stdio.h>
#include <stdlib.h>
typedef int TElement;
typedef struct BinTrNode {
TElement data; // 노드에 저장할 데이터
struct BinTrNode* left; // 왼쪽 자식 노드의 포인터
struct BinTrNode* right; // 오른쪽 자식 노드의 포인터
} TNode;
TNode* root; // root node의 포인터
void init_tree() {root = NULL;}
int is_empty_tree() {return root == NULL;}
TNode* get_root() {return root;}
TNode* create_tree(TElement val, TNode* l, TNode* r)
{
TNode* n = (TNode*)malloc(sizeof(TNode));
n -> data = val;
n -> left = l;
n -> right = r;
return n;
}
전위 순회
// 전위 순회
void preorder(TNode *n)
{
if (n != NULL) {
printf("[%d]", n -> data);
preorder((n -> left));
preorder((n -> right));
}
}
중위 순회
// 중위 순회
void inorder(TNode * n) {
if (n != NULL) {
inorder(n -> left);
printf("[%d]", n -> data);
inorder(n -> right);
}
}
후위 순회
// 후위 순회
void postorder(TNode *n)
{
if (n != NULL) {
postorder(n -> left);
postorder(n -> right);
printf("[%d]", n -> data);
}
}
레벨 순회
// 레벨 순회
#define MAX_QUEUE_SIZE 100
typedef TNode* Element;
Element data[MAX_QUEUE_SIZE];
int front;
int rear;
void error(char str[])
{
printf("%s\n, str");
exit(1);
}
void init_queue() {front = rear = 0;}
int is_empty() {return front == rear;};
int is_full() {return front==(rear + 1) % MAX_QUEUE_SIZE;}
int size() {return(rear - front + MAX_QUEUE_SIZE) % MAX_QUEUE_SIZE;}
void enqueue(Element val)
{
if(is_full()) error("큐 포화 에러");
rear = (rear + 1) % MAX_QUEUE_SIZE;
data[rear] = val;
}
Element dequeue()
{
if (is_empty()) error("큐 공백 에러");
front = (front + 1) % MAX_QUEUE_SIZE;
return data[front];
}
Element peek()
{
if(is_empty()) error("큐 공백 에러");
return data[(front + 1) % MAX_QUEUE_SIZE];
}
void levelorder(TNode *root)
{
TNode* n;
if (root == NULL) return;
init_queue();
enqueue(root);
while (!is_empty()) {
n = dequeue();
if (n != NULL) {
printf("[%d]", n -> data);
enqueue(n -> left);
enqueue(n -> right);
}
}
}
int main()
{
TNode *a, *b, *c, *d, *e, *f, *g, *h, *i, *j;
init_tree();
g = create_tree(7, NULL, NULL);
h = create_tree(8, NULL, NULL);
i = create_tree(9, NULL, NULL);
j = create_tree(10, NULL, NULL);
f = create_tree(6, NULL, NULL);
d = create_tree(4, g, h);
e = create_tree(5, i, j);
b = create_tree(2, d, e);
c = create_tree(3, NULL, f);
a = create_tree(1, b, c);
printf("\n In-Order : "); inorder(a);
printf("\n Pre-Order : "); preorder(a);
printf("\n Post-Order : "); postorder(a);
printf("\nlevel-Order : "); levelorder(a);
printf("\n");
return 0;
}
// 출력 결과
// In-Order : [7][4][8][2][9][5][10][1][3][6]
// Pre-Order : [1][2][4][7][8][5][9][10][3][6]
// Post-Order : [7][8][4][9][10][5][2][6][3][1]
// level-Order : [1][2][3][4][5][6][7][8][9][10]