Chap8. Tree
- 대부분의 비선형 자료구조는 표현을 목적으로 하며 Tree 구조 또한 그렇다.
1. 트리의 개요
(1) 이진트리
- 연결리스트 : 노드를 추가하지 않고 데이터를 삽입했다.
- 트리 : 구조체를 만들고 삽입, 삭제, 추출 등 구조체를 위한 도구가 될 함수를 만들 것이다. 방향보다는 서로간 연결이 더 중요하다. 각자 서로의 주소값을 알고 있으면 연결정보가 있는 것이다.
- 계층적 관계(Hierarchical Relationship) 을 표현하는 자료구조
- 데이터 표현을 위한 도구
- 노드 node: 트리의 구성요소, 실제 data를 담을 수 있는 구조
- 간선 edge: 노드 간 연결선
- 루트 노드 root node: 트리 최상위 노드
- 단말 노드 terminal code : 아래로 다른 노드가 연결되어 있지 않는 노드
- 내부 노드 internal node : 단말 노드를 제외한 모든 노드
- 노드간 관계 : 부모 노드, 자식 노드, 형제 노드
- 서브 트리 : 각 트리는 또다른 서브 트리로 이뤄져 있고 그 주고는 재귀적이다.
- 이진트리의 조건
1. 루트 노드를 중심으로 두 개의 서브 트리로 나뉘어 진다.
2. 나뉘어진 두 서브트리도 모두 이진트리어어야 한다.
- 이진 트리는 구조가 재귀적 이고, 트리와 관련된 연산은 재귀적으로 사고하고 재귀적으로 구현할 필요가 있다.
(2) 공집합 노드
- 이진 트리의 재귀성을 계속하기 위하여 공집합(Empty Set)도 노드로 간주한다.
- 트리의 높이와 레벨의 최대값은 같다
- 포화 이진 트리 : 모든 레벨에 노드가 꽉찬 트리
- 완전 이진 트리 : 빈 틈 없이 채워진 이진 트리 ⇒ 위에서 아래로 왼쪽에서 오른쪽으로 채워진 트리
2. 이진 트리의 구현
- 이진 트리 활용 도구 만들기
- 배열 기반 : 노드에 번호를 부여하고 그 번호에 해당하는 값을 배열의 인덱스 값으로 활용, 편의상 배열의 첫 번째 요소는 사용하지 않는다.
- 연결리스트 기반 : 트리의 구조와 리스트의 연결 구조가 일치한다. → 직관적 이해
1
2
3
4
5
6
|
typedef struct _bTreeNode
{
BTData data;
struct _bTreeNode * left;
struct _bTreeNode * right;
} BTreeNode;
|
- 이진 트리의 모든 노드는 직/간접적으로 연결되어있다. 따라서 루트 노드의 주소값 만 기억하면, 이진 트리 전체를 가리키는 것과 다름없다.
- 논리적으로도 하나의 노드는 그 자체로 이진 트리다. 따라서 노드를 표현한 구조체는 실제로 이진 트리를 표현한 구조체이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
BTreeNode * MakeBTreeNode(); // 노드의 생성
// 노드를 동적으로 할당하고 SetData 함수로 채우고, left & right는 NULL로 자동 초기화
BTData GetData(BTreeNOde * bt); // 노드에 저장된 데이터 반환
void SetData(BTreeNode * bt, BTData data) // 노드에 데이터 저장
// 노드에 직접 접근하는 것 보다 함수를 통한 접근이 보다 좋은 접근
// 구조체가 정의 되면 관련 연산은 함수를 통하는 것이 좋다.
// 하나의 노드도 서브트리 이므로 SubTree로 이름 짓는 것이 타당
BTreeNode * GetLeftSubTree(BTreeNode * bt); // 왼쪽 서브 트리의 주소 값 반환
BTreeNode * GetRightSubTree(BTreeNode * bt); // 오른쪽 서브 트리의 주소 값 반환
void MakeLeftSubTree(BTreeNode * main, BTreeNode * sub); // main의 서브 왼쪽 서브 트리로 sub 연결
void MakeRightSubTree(BTreeNode * main, BTreeNode * sub); // main의 오른쪽 서브 트리로 sub 연결
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
int main()
{
BTreeNode * ndA = MakeBTreeNode(); // 노드 A 생성
BTreeNode * ndB = MakeBTreeNode(); // 노드 B 생성
BTreeNode * ndC = MakeBTreeNode(); // 노드 C 생성
// ndA, ndB, ndC를 이용한 SetData 함수 호출을 통해 데이터를 채운 후...
// 노드 A의 왼쪽 자식 노드로 노드 B 연결
MakeLeftSubTree(ndA, ndB);
// 노드 A의 오른쪽 자식 노드로 노드 C 연결
MakeRightSubTree(ndA, ndB);
....
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
BTreeNode * MakeBTreeNode()
{
BTreeNode * nd = (BTreeNode *)malloc(sizeof(BTreeNode));
nd->left = NULL;
nd->right = NULL;
return nd;
}
BTData GetData(BTreeNode * bt)
{
return bt->data;
}
void SetData(BTreeNode * bt, BTData data)
{
bt->data = data;
}
BTreeNode * GetLeftSubTree(BTreeNode * bt)
{
return bt->left;
}
BTreeNode * GetRightSubTree(BTreeNode * bt)
{
return bt->right;
}
void MakeLeftSubTree(BTreeNode * main, BTreeNode * sub)
{
if(main->left != NULL)
free(main->left); // 만약 하부 노드가 있었으면 메모리 누수
main->left = sub;
}
void MakeRightSubTree(BTreeNode * main, BTreeNode * sub)
{
if(main->right != NULL)
free(main->right);
main->right = sub;
}
|
3. 이진 트리의 순회(Travelsal)
- 순회의 세가지 방법 : 중위, 후위, 전위 ⇒ 루트 노드를 방문하는 시점에 따라서 나뉘어 짐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
// 이진트리 중위순회
void InorderTraverse(BTreeNode * bt)
{
InorderTraverse(bt->left);
printf("%d \n", bt->data); // root
InorderTraverse(bt->right);
// 노드에 트리가 들어오는 경우 & 탈출 경우 없음
}
// 재귀 탈출 조건 포함
void InorderTraverse(BTreeNode * bt)
{
if(bt==NULL) // 노드가 단말 노드이면 단말 노드의 자식노드는 NULL (들어가봐야 앎)
return;
InorderTraverse(bt->left);
printf("%d \n", bt->data); // root
InorderTraverse(bt->right);
}
// 전위 순회
void PreorderTraverse(BTreeNode * bt)
{
if(bt==NULL)
return;
printf("%d \n", bt->data); // root
InorderTraverse(bt->left);
InorderTraverse(bt->right);
}
// 후위 순회
void PostorderTraverse(BTreeNode * bt)
{
if(bt==NULL)
return;
InorderTraverse(bt->left);
InorderTraverse(bt->right);
printf("%d \n", bt->data); // root
}
|
- 함수 포인터를 이용한 노드의 방문이유 구성하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// 함수 포인터 형 정의
typedef void VisitFuncPtr(BTData data) // (* VisitFuncPtr 가능)
void InorderTraverse(BTreeNode * bt, VisitFuncPtr action)
{
if(bt == NULL)
return;
InorderTraverse(bt->left, action);
action(bt->data); // 노드의 방문
InorderTraverse(bt->right, action);
}
void ShowIntData(int data) // BTData가 int 가정
{
printf("%d ", data);
// 목적을 사용자가 직접 정의하는 부분
}
|
4. 수식 트리 (Expression Tree)의 구현
- 수식 트리의 이해 : 중위 표기법의 수식을 수식 트리로 변환하는 프로그램의 작성이 목적!
- 중위 표기법 수식은 사람이 인식하기 좋은 수식이나 컴퓨터는 인식이 어렵다.
- 수식 트리는 해석이 쉽다.
- 연산자 우선순위를 고려하지 않아도 되고, In & Post & Pre로 면환이 쉽다.
중위 표기법 수식 → 후위 표기법 수식 → 수식 트리
1
2
3
4
5
6
7
8
9
10
|
#include "BinaryTree2.h"
BTreeNode * MakeExpTree(char exp[]); // 수식 트리 구성
// 후위 표기법의 수식을 인자로 받아서 수식 트리를 구성하고 루트 노드의 주소 값을 반환한다.
int EvaluateExpTree(BTreeNode * bt); // 수식 트리 계산
void ShowPrefixTypeExp(BTreeNode * bt); // 전위 표기법 기반 출력
void ShowInfixTypeExp(BTreeNode * bt); // 중위 표기법 기반 출력
void ShowPostfixTypeExp(BTreeNode * bt); // 후위 표기법 기반 출력
|
- 후위 표기법의 수식에서 먼저 등장하는 피연산자와 연산자를 이용해서 트리의 하단부터 구성해 나가고 이어서 윗부분을 구성해나간다.
- 차례차례 스택을 쌓는다. 서브트리가 있다면 스택에 그대로 쌓으면 된다. → 루트 노드가 쌓이면 그 자체로 서브트리로 인식
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
BTreeNode * MakeExpTree(char exp[])
{
/*
- 피연산자는 스택으로 옮긴다.
- 연산자를 만나면 스택에서 두 개의 피연산자 꺼내 자식노드로 연결
- 자식 노드를 연결해서 만들어진 트리는 다시 스택으로 옮긴다.
- 완성 결과를 스택에 저장하고, 수식트리의 root node 주소값을 반환한다.
*/
Stack stack;
BTreeNode * pnode;
int expLen = strlen(exp);
int i;
for(i=0; i<expLen; i++)
{
pnode = MakeBTreeNode();
if(isdigit(exp[i])) // 피연산자라면
{
SetData(pnode, exp[i]-'0'); // 문자를 정수로 변경
}
else // 연산자라면
{
MakeRightSubTree(pnode, SPop(&stack));
MakeLeftSubTree(pnode, SPop(&stack));
SetData(pnode, exp[i]);
}
SPush(&stack, pnode);
}
return SPop(&stack);
}
// 계산 기본구성
int EvaluateExpTree(BTreeNode * bt)
{
int op1, op2;
// 단말노드라면 바로 탈출
if(GetLeftSubTree(bt)==NULL && GetRightSubTree(bt)==NULL)
return GetData(bt);
// SubTree 도 올 수 있으므로 재귀적 표현 필요!
op1 = EvaluateExpTree((GetLeftSubTree(bt)));
op2 = EvaluateExpTree((GetRightSubTree(bt)));
switch(GetData(bt))
{
case '+':
return op1+op2;
case '-':
return op1-op2;
case '*':
return op1*op2;
case '/':
return op1/op2;
}
return 0;
}
|