자료구조 - 이진트리 학습

이진트리(Binary Tree)란?

  • 어떤 노드의 자식 노드의 수가 최대 2개인 트리

  • 공집합 혹은 루트(root)와 왼쪽 서브 트리, 오른쪽 서브 트리로 이루어진 유한 집합, 각각의 서브 트리는 모두 이진 트리이다.

  • 트리는 connected acyclic graph

    • 루트 노드(root)를 반드시 가진다.
    • 트리를 구성하는 노드 간 단순 경로가 존재한다.
  • 트리는 1개 이상의 노드로 이루어진 유한 집합

    • 루트 노드(root)를 반드시 가진다.
    • 나머지 노드들은 분리집합 T1, …, Tn으로 분할 가능
      • T1, T2 등은 각각의 하나의 트리(서브 트리)가 된다.

트리 용어

  • 차수(degree): 어떤 노드의 자식 노드의 개수
  • 트리의 차수(degree of a tree): 트리에 있는 노드의 최대 차수
  • 리프 노드(leaf node): 차수가 0인 노드, 즉 자식이 없다.
    • 단말노드(terminal node)라고도 부른다.
  • 레벨(level): 루트의 레벨을 1로 하고 자식으로 내려가면서 하나씩 더한다.
  • 트리의 높이(height) or 깊이(depth): 트리가 가지는 최대 레벨
  • 포레스트(forest): 루트 노드를 없앤 후 얻은 서브 트리의 집합
  • 엣지(edge): 노드와 노드를 연결한 선
  • 노드(node)와 엣지(edge)의 관계
    • 노드의 개수: n
    • 엣지의 개수: e
    • e = n - 1

이진 트리의 특징

  • 레벨 l에서 최대 노드 수: 2^(l-1)
  • 높이가 h인 이진 트리의 최대 노드 수: 2^h - 1
  • 높이가 h인 이진 트리의 최소 노드 수: h

이진 트리의 종류

  • 포화 이진 트리(full binary tree)
    • 높이가 h이면 노드 수가 2^h - 1 인 트리(모든 레벨이 꽉 차 있다.)
  • 완전 이진 트리(complete binary tree)
    • 높이가 h이면 level h-1 까지 노드 수는 2^(h-1) - 1 개 이고, level h 에서는 왼쪽부터 오른쪽으로 노드가 채워져 있는 트리
  • 편향 이진 트리(skewed binary tree)
    • 왼쪽이나 오른쪽 서브 트리만 가지는 트리

순회(traversal)

  • 전위 순회 (stack으로 구현)
    Screenshot from 2019-07-05 01-21-25*

  • 중위 순회 (stack으로 구현)
    Screenshot from 2019-07-05 01-21-27

  • 후위 순회 (stack으로 구현)
    Screenshot from 2019-07-05 01-21-30

  • 레벨 순회 (queue로 구현)
    Screenshot from 2019-07-05 01-21-31

파이썬으로 이진트리 구현

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# traversal (순회)
# 재방문 없이 어떤 자료구조의 모든 데이터(노드) 방문하는 것
class Stack:
def __init__(self):
self.container=list()

def empty(self):
if not self.container:
return True
else:
return False

def push(self, data):
self.container.append(data)

def pop(self):
return self.container.pop()

def peek(self):
return self.container[-1]

from queue import Queue
# stack 모듈 지원안하면서 queue는 모듈 지원하는 이유
# stack은 append()와 pop()을 통해 성능좋게 만들 수 있으나,
# queue는 pop(0)할 경우, 성능이 상당히 안좋아진다. --> 동적 배열의 단점

class TreeNode:
def __init__(self, data):
self.data = data
self.left = None
self.right = None

# 재귀함수 이용한 전위순회
def preorder(node):
# base case
if not node:
return
# 방문
print(node.data, end=" ")
# 왼쪽 자식
preorder(node.left)
# 오른쪽 자식
preorder(node.right)

# 재귀함수 이용한 중위순회
def inorder(node):
if not node:
return

inorder(node.left)
print(node.data, end=" ")
inorder(node.right)

# 재귀함수 이용한 후위순회
def postorder(node):
if not node:
return

postorder(node.left)
postorder(node.right)
print(node.data, end=" ")

# while문 이용한 전위순회
def iter_preorder(cur):
# 1. 처음에 cur.data 출력 및 stack에 쌓는다
# 2. 왼쪽 자식이 있으면 계속 stack에 push하고, 없으면 pop 시킨 뒤,
# 3. 부모 노드로 cur 이동하여 cur의 오른쪽 자식이 있으면 stack에 push하고
# 4. 없으면, pop시킨다.
# 2번 부터 반복
s = Stack()

while True:
while cur:
# 방문
print(cur.data, end=" ")
s.push(cur)
cur = cur.left
if s.empty():
break
cur = s.pop()
cur = cur.right

# while문 이용한 중위순회
def iter_inorder(cur):
s = Stack()

while True:
while cur:
s.push(cur)
cur = cur.left
if s.empty():
break
cur = s.pop()
# 방문
print(cur.data, end=" ")
cur = cur.right

# while문 이용한 후위순회
def iter_postorder(cur):
# 1. 스택 1, 2 생성하고,
# 2. 처음 노드 stack에 push하고,
# 3. 스택1이 비어있지 않는 동안, 스택1 왼쪽, 오른쪽 자식있으면 스택1에 push하고 pop된것을 스택2에 push하고,
# 4. 없으면, pop시킨 값을 스택2에 push한다.
# 5. 스택1 비어있다면, break로 빠져나와 s2 스택 비어있을 때까지 pop하여 print한다.
s1 = Stack()
s2 = Stack()

s1.push(cur)
while not s1.empty():
cur = s1.pop()
if cur.left:
s1.push(cur.left)
if cur.right:
s1.push(cur.right)
s2.push(cur)

while not s2.empty():
cur = s2.pop()
print(cur.data, end=" ")

# while문 이용한 레벨순회
def levelorder(cur):
# queue에 cur 집어넣고, cur를 dequeue(get) 함과 동시에 cur.left 또는 cur.right 있으면
# 순서대로 queue 에 enqueue(put)해주고,
# q가 비어있지 않을 때까지 while문 반복
q = Queue()
q.put(cur) # q.enqueue(cur)
while not q.empty():
cur = q.get() # q.dequeue()
print(cur.data, end=" ")
if cur.left:
q.put(cur.left)
if cur.right:
q.put(cur.right)


if __name__ == "__main__":
n1 = TreeNode(1)
n2 = TreeNode(2)
n3 = TreeNode(3)
n4 = TreeNode(4)
n5 = TreeNode(5)
n6 = TreeNode(6)
n7 = TreeNode(7)

n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
n3.left = n6
n3.right = n7

print("전위순회 - 재귀함수")
preorder(n1)
print()
print("중위순회 - 재귀함수")
inorder(n1)
print()
print("후위순회 - 재귀함수")
postorder(n1)
print()
print("--------------------")
print("전위순회 - while문")
iter_preorder(n1)
print()
print("중위순회 - while문")
iter_inorder(n1)
print()
print("후위순회 - while문")
iter_postorder(n1)
print()
print("레벨순회 - while문")
levelorder(n1)
print()