이진 트리 (binary tree) 는 노드가 최대 두 개의 자식 노드 (왼쪽, left 와 오른쪽, right) 를 갖는 자료구조이다. 자식 노드는 부모 노드에 대한 참조를 포함할 수 있다. 트리의 루트 노드 (root node) 는 모든 노드의 조상이다.
이진 트리에서 노드의 차수는 최대 2다. 트리에 m개의 내부 노드가 있으며, 각 내부 노드에 두 개의 자식 노드가 있다고 가정한다. 또한 트리에 n개의 말단 노드가 있다면, 트리의 차수는 n-1 이다.
즉, 2m = n + m -1 이며, 따라서 m = n - 1 이다. 여기서 m은 내부 노드의 개수, n은 말단 노드의 개수이다.
용어
그래프와 마찬가지로, 트리도 기본적인 용어를 먼저 살펴보자.
- 노드 차수 (degree): 자식 수
- 경로 (path): 한 노드 (부모) 에서 다른 노드 (자식) 에 이르는 노드들의 순서
- 경로 길이 (length): 한 노드에서 다른 노드로 가는 간선의 수. 시작 노드와 끝 노드가 같다면 길이는 0이다.
- 형제 노드 (sibling node): 부모가 같은 두 노드
- 외부 (external) 노드 (말단 노드): 자식이 없는 노드 (차수가 0인 노드)
- 내부 (internal) 노드 (가지 노드): 자식이 있는 노드 (차수가 0이 아닌 노드)
- 노드 깊이 (depth): 루트 노드에서 어떤 노드로 가는 경로의 길이. 루트 노드의 깊이는 0이다.
- 노드 레벨 (level): 노드 깊이 + 1. 즉 루트 노드의 레벨은 1이다.
- 노드 높이 (height): 한 노드와 단말 노드 사이의 최대 경로 길이
- 크기 (size): 모든 노드의 수
포화 이진 트리
포화 이진 트리 (perfect binary tree) 는 모든 내부 노드가 두 개의 자식 노드를 가지며 모든 말단 노드가 같은 깊이 또는 레벨을 가지는 이진 트리이다. 예를 들어 아래와 같은 이진 트리를 말한다.
포화 이진 트리의 높이 (h) 와 말단 노드 수 (n) 의 관계를 그림으로 나타내면 다음과 같다.
또한, 포화 이진 트리의 총 노드 수는 2^(h+1) - 1 이다.
완전 이진 트리
완전 이진 트리 (complete binary tree) 는 마지막 레벨은 제외한 모든 레벨이 완전히 채워져 있으며, 마지막 레벨의 모든 말단 노드는 왼쪽에 있다.
이진 트리 구현
이진 트리를 구현하는 가장 단순한 방법은 리스트를 사용하는 것이다. 다음 코드는 루트 노드와 두 개의 빈 하위 리스트가 있는 리스트를 만든다. 루트 노드의 왼쪽에 서브 트리를 추가하려면 루트 노드의 리스트 두 번째 위치에 새 리스트를 삽입하면 된다. 그러나 이 코드는 리스트 중간에 노드를 삽입하거나 꺼낼 때 제한이 있어 매우 비효율적이다.
def BinaryTreeList(r):
return [r, [], []]
def insertLeft(root, newBranch):
t = root.pop(1)
if len(t) > 1:
root.insert(1, [newBranch, t, []])
else:
root.insert(1, [newBranch, [], []])
return root
def insertRight(root, newBranch):
t = root.pop(2)
if len(t) > 1:
root.insert(2, [newBranch, [], t])
else:
root.insert(2, [newBranch, [], []])
return root
def getRootVal(root):
return root[0]
def setRootVal(root, newVal):
root[0] = newVal
def getLeftChild(root):
return root[1]
def getRightChild(root):
return root[2]
r = BinaryTreeList(3)
insertLeft(r, 4)
insertLeft(r, 5)
insertRight(r, 6)
insertRight(r, 7)
print(getRootVal(r))
print(getLeftChild(r))
print(getRightChild(r))
결과
PS C:\Users\bumsu\Desktop\Python\DSA> & C:/Python38/python3.exe "c:/Users/bumsu/Desktop/Python/DSA/Algorithm/13. 이진 트리/BT_lists.py"
3
[5, [4, [], []], []]
[7, [], [6, [], []]]
그러나 이 코드는 노드의 검색, 추가 등의 작업이 매우 비효율적이다.
다음 코드는 이진 트리를 클래스로 표현한다. 이진 트리의 노드는 왼쪽과 오른쪽 자식 노드에 대한 특성을 가진다. 노드의 두 자식 노드를 검사하여 값이 없을 경우 말단 노드인지 확인할 수 있다.
# 높이
class Height(object):
def __init__(self):
self.height = 0
# 노드 클래스
class NodeBT(object):
def __init__(self, value=None, level=1):
self.value = value
self.level = level
self.left = None
self.right = None
def __repr__(self):
return "{}".format(self.value)
def _add_next_node(self, value, level_here=2):
new_node = NodeBT(value, level_here)
if not self.value:
self.value = new_node
elif not self.left:
self.left = new_node
elif not self.right:
self.right = new_node
else:
# 왼쪽 오른쪽 자식이 모두 있다면 왼쪽 자속 노드에 새 노드 추가
self.left = self.left._add_next_node(value, level_here + 1)
return self
def _search_for_node(self, value):
if self.value == value:
return self
else:
found = None
if self.left:
found = self.left._search_for_node(value)
if self.right:
found = found or self.right._search_for_node(value)
return found
def _is_leaf(self):
# 말단 노드라면 왼쪽 오른쪽 자식이 모두 없다.
return not self.right and not self.left
def _get_max_height(self):
heightr, heightl = 0, 0
if self.right:
heightr = self.right._get_max_height() + 1
if self.left:
heightl = self.left._get_max_height() + 1
return max(heightr, heightl)
def _is_balanced(self, height=Height()):
# 균형 트리인지 확인 - O(n)
lh = Height()
rh = Height()
if self.value is None:
return True
l, r = True, True
if self.left:
l = self.left._is_balanced(lh)
if self.right:
r = self.right._is_balanced(rh)
height.height = max(lh.height, rh.height) + 1
if abs(lh.height - rh.height) <= 1:
return l and r
return False
def _is_bst(self, left=None, right=None):
# 이진 탐색 트리인지 확인 - O(n)
if self.value:
if left and self.value < left:
return False
if right and self.value > right:
return False
l, r = True, True
if self.left:
l = self.left._is_bst(left, self.value)
if self.right:
r = self.right._is_bst(self.value, right)
return l and r
else:
return True
# 이진 트리 클래스
class BinaryTree(object):
def __init__(self):
self.root = None
def add_node(self, value):
if not self.root:
self.root = NodeBT(value)
else:
self.root._add_next_node(value)
def is_leaf(self, value):
node = self.root._search_for_node(value)
if node:
return node._is_leaf()
else:
return False
def get_node_level(self, value):
node = self.root._search_for_node(value)
if node:
return node.level
else:
return False
def is_root(self, value):
return self.root.value == value
def get_height(self):
return self.root._get_max_height()
def is_balanced(self):
return self.root._is_balanced()
def is_bst(self):
return self.root._is_bst()
bt = BinaryTree()
for i in range(1, 10):
bt.add_node(i)
print(bt.get_node_level(8))
결과
PS C:\Users\bumsu\Desktop\Python\DSA> & C:/Python38/python3.exe "c:/Users/bumsu/Desktop/Python/DSA/Algorithm/13. 이진 트리/binary_tree.py"
5
출처
파이썬 자료구조와 알고리즘 - 한빛미디어, 미아 스타인
'Python > 그래프와 트리 (Graph and Tree)' 카테고리의 다른 글
14. 트리 순회 (Tree Traversal): 파이썬 자료구조와 알고리즘 (0) | 2020.08.14 |
---|---|
13-2. 이진 탐색 트리와 자가 균형 이진 탐색 트리 (Binary Search Tree and Self-balancing Binary Tree): 파이썬 자료구조와 알고리즘 (0) | 2020.08.13 |
12. 그래프 기초 (Graph Basics): 파이썬 자료구조와 알고리즘 (0) | 2020.08.11 |