이진트리의 노드를 정의하는 클래스인 Node
를 구현해봅시다. 각 노드는 다음과 같은 속성을 가지고 있습니다.
key
: 노드의 키 값left
: 현재 노드의 왼쪽 자식 노드를 가리키는 포인터right
: 현재 노드의 오른쪽 자식 노드를 가리키는 포인터parent
: 현재 노드의 부모 노드를 가리키는 포인터
class Node:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
self.parent = None
이제 BST 클래스를 구현해봅시다.
BST 클래스는 다음과 같은 메서드로 정의되어 있습니다.
tree_insert()
: 새로운 노드를 삽입합니다.tree_search()
: 트리에서 특정 키를 가진 노드를 찾습니다.iterative_tree_search()
: 반복문을 사용하여 트리에서 원하는 키값을 가진 노드를 찾습니다. tree_minimum()
: 서브트리에서 최소값을 찾습니다.tree_maximum()
: 서브트리에서 최대값을 찾습니다.tree_successor()
: 특정 노드의 후속자를 찾습니다.inorder_tree_walk()
: 트리를 중위 순회합니다.transplant()
: 한 노드를 다른 노드로 대체합니다.tree_delete()
: 트리에서 특정 노드를 삭제합니다.print_tree()
: 이진 탐색 트리를 인쇄합니다.전체 코드는 다음과 같습니다.
class BST:
def __init__(self):
self.root = None
def tree_insert(self, z):
y = None
x = self.root
while x is not None:
y = x
if z.key < x.key:
x = x.left
else:
x = x.right
z.parent = y
if y is None:
self.root = z
elif z.key < y.key:
y.left = z
else:
y.right = z
def tree_search(self, x, k):
if x is None or k == x.key:
return x
if k < x.key:
return self.tree_search(x.left, k)
else:
return self.tree_search(x.right, k)
def iterative_tree_search(self, x, k):
while x is not None and k != x.key:
if k < x.key:
x = x.left
else:
x = x.right
return x
def tree_minimum(self, x):
while x.left is not None:
x = x.left
return x
def tree_maximum(self, x):
while x.right is not None:
x = x.right
return x
def tree_successor(self, x):
if x.right is not None:
return self.tree_minimum(x.right)
y = x.parent
while y is not None and x == y.right:
x = y
y = y.parent
return y
def inorder_tree_walk(self, x):
if x is not None:
self.inorder_tree_walk(x.left)
print(x.key, end=' ')
self.inorder_tree_walk(x.right)
def transplant(self, u, v):
if u.parent is None:
self.root = v
elif u == u.parent.left:
u.parent.left = v
else:
u.parent.right = v
if v is not None:
v.parent = u.parent
def tree_delete(self, z):
# Determine which node to splice out: either z or z’s successor
if z.left is None or z.right is None:
y = z # case 0 or 1
else:
y = self.tree_successor(z) # case 2
# Set x to a non-NIL child of y, or to NIL if y has no children
if y.left is not None:
x = y.left
else:
x = y.right
# y is removed from the tree by manipulating pointers of p[y] and x
if x is not None:
x.parent = y.parent
if y.parent is None:
self.root = x
elif y == y.parent.left:
y.parent.left = x
else:
y.parent.right = x
# If z’s successor was spliced out, copy its data into z
if y != z:
z.key = y.key
# Here you would also copy any other satellite data from y to z
return y
def print_tree(self):
self._print_tree_recursive(self.root, 0)
def _print_tree_recursive(self, node, level):
if node is None:
return
self._print_tree_recursive(node.right, level + 1)
print(' ' * level + str(node.key))
self._print_tree_recursive(node.left, level + 1)
각각의 함수과 코드에 대해 설명해보겠습니다.
tree_insert()
def tree_insert(self, z):
y = None
x = self.root
while x is not None:
y = x
if z.key < x.key:
x = x.left
else:
x = x.right
z.parent = y
if y is None:
self.root = z
elif z.key < y.key:
y.left = z
else:
y.right = z
tree_insert
메서드를 구현한 것입니다. 이 메서드는 주어진 노드 z
를 트리에 삽입합니다. 코드의 동작은 아래와 같습니다. x
를 루트부터 시작하고, x
가 None이 될 때까지 반복하여 적절한 위치를 찾습니다. 동시에 y
는 x
의 부모 노드를 나타냅니다.x
가 None이 되어 탐색이 종료되면, y
가 새로운 노드 z
의 부모 노드가 됩니다. 따라서 z.parent
를 y
로 설정합니다.y
가 None이라면, 삽입할 노드가 트리의 첫 번째 노드이므로 z
를 트리의 루트로 설정합니다.z
의 키 값이 y
의 키 값보다 작으면 z
를 y
의 왼쪽 자식으로 삽입합니다.z
의 키 값이 y
의 키 값보다 크거나 같으면), z
를 y
의 오른쪽 자식으로 삽입합니다.이러한 방식으로 tree_insert()
는 이진 탐색 트리의 성질을 유지하면서 새로운 노드를 적절한 위치에 삽입합니다.
tree_search()
이진탐색트리에서 특정 키k
를 찾기 위해 사용되는 재귀함수입니다.
코드의 동작은 아래와 같습니다.
x
가 None
인 경우, 이것은 트리의 끝에 도달하여 키를 찾지 못한 경우입니다. 이 경우에는 해당 서브트리에 키가 없음을 나타내기 위해 None
을 반환합니다.x
의 키가 대상 키 k
와 같은 경우, 우리는 원하는 키가 있는 노드를 찾았으며 이 노드를 반환합니다. def tree_search(self, x, k):
if x is None or k == x.key:
return x
if k < x.key:
return self.tree_search(x.left, k)
else:
return self.tree_search(x.right, k)
iterative_tree_search()
이진 탐색 트리(BST)에서 특정 키 k
를 반복적으로 탐색하는 함수입니다.
코드의 동작은 아래와 같습니다.
while
루프를 사용하여 노드 x
가 None
이 아니고 대상 키 k
가 현재 노드 x
의 키와 다를 때까지 반복합니다. 이는 트리를 왼쪽이나 오른쪽으로 순회하면서 키를 찾거나 끝까지 도달할 때까지 반복합니다.x
의 키와 대상 키 k
를 비교하여 다음 이동할 방향을 결정합니다.k
가 현재 노드의 키보다 작으면 왼쪽 서브트리로 이동합니다.k
가 현재 노드의 키보다 크면 오른쪽 서브트리로 이동합니다.k
가 현재 노드의 키와 같다면, 원하는 키를 찾았으므로 루프를 종료합니다.x
를 반환합니다. 이는 대상 키를 찾았을 경우 해당 키를 가진 노드를 반환하고, 찾지 못했을 경우 None
을 반환합니다. def iterative_tree_search(self, x, k):
while x is not None and k != x.key:
if k < x.key:
x = x.left
else:
x = x.right
return x
tree_minimum()
, tree_maximum()
이진 탐색 트리(BST)에서 특정 노드 x
를 루트로 하는 서브트리에서 가장 크거나, 가장 작은 키 값을 가진 노드를 찾는 함수입니다.
tree_minimum()
의 동작은 아래와 같습니다. tree_maximum()
의 경우에도 왼쪽과 오른쪽만 바꾸어 생각하면 됩니다.
x
의 왼쪽 자식이 None
이 될 때까지 반복합니다. 이 과정은 x
의 왼쪽 자식이 없는, 즉 현재 노드가 해당 서브트리에서 가장 작은 키를 가진 노드임을 보장합니다.x
를 반환합니다. 이는 가장 작은 키를 가진 노드를 찾았음을 의미합니다.# tree_minimum
def tree_minimum(self, x):
while x.left is not None:
x = x.left
return x
# tree_maximum
def tree_maximum(self, x):
while x.right is not None:
x = x.right
return x
tree_successor()
노드 x의 후계자를 찾습니다. 후계자란, BST에서 주어진 노드 ‘x’ 보다 크면서 가장 작은 값을 가진 노드를 의미합니다.
이 함수는 BST에서 삭제 연산을 수행할 때 유용하게 사용됩니다.
BST의 삭제연산에 대해 미리 추가적인 설명을 해보자면, BST의 삭제 연산은 다음과 같이 세 가지 경우로 나누어집니다.
tree_successor()
는 위 세가지 경우 중 Case 2에서만 사용되며, 이 경우 삭제할 노드를 대체할 successor를 찾아야 합니다. 후계자는 삭제할 노드를 대체하여 BST의 특성을 유지하면서 빈 자리를 채우는 역할을 합니다.
#참고
💡 **Case 2 :** 삭제 연산 시 successor 를 찾는 과정tree_successor()
를 호출하여 수행됩니다.위의 과정 중 첫번째 과정에 tree_successor()
는 BST에서 삭제 연산을 수행할 때 후계자를 찾는 데 사용됩니다.
tree_successor
의 동작방식은 아래와 같습니다.
먼저 주어진 노드 x
의 오른쪽 서브트리가 비어있지 않은지 확인합니다. 오른쪽 서브트리가 비어있지 않다면, 그 서브트리에서 가장 작은 값을 찾기 위해 tree_minimum
메서드를 호출합니다. 이 값이 후계자가 됩니다.
만약 오른쪽 서브트리가 비어있는 경우, 후계자를 찾기 위해 부모 노드를 따라 올라가면서 검색합니다. 이때, 다음의 두 가지 경우가 있습니다
x
가 오른쪽 자식 노드인 경우: 이 경우에는 계속해서 부모 노드로 이동합니다.x
가 왼쪽 자식 노드인 경우: 이 경우에는 현재 노드 x
의 부모 노드가 바로 후계자가 됩니다.def tree_successor(self, x):
if x.right is not None:
return self.tree_minimum(x.right)
y = x.parent
while y is not None and x == y.right:
x = y
y = y.parent
return y
inorder_tree_walk()
BST의 모든 노드들이 키 값의 오름차순으로 출력해주는 함수입니다. BST의 중요한 특징 중 하나인 키의 순서적 정렬을 확인할 수 있는 방법입니다.
이진 탐색 트리를 중위 순회한 모든 노드의 키 값을 키 순서대로 출력합니다. 왼쪽 서브 트리부터 시작해 현재 노드의 값을 출력하고, 마지막으로 오른쪽 서브 트리를 방문하는 방식입니다.
함수의 작동 방식은 아래와 같습니다.
x
가 None
이 아닌 경우에만 실행됩니다.
왼쪽 서브 트리를 순회하기 위해 self.inorder_tree_walk(x.left)
를 호출합니다. 왼쪽 서브 트리의 가장 왼쪽 노드까지 내려가게 되고, 그 노드부터 시작하여 순차적으로 값을 출력합니다.
다음으로 현재 노드의 값을 출력하고, 이어서 오른쪽 서브 트리를 순회하기 위해
self.inorder_tree_walk(x.right)
를 호출합니다. 그 후, 오른쪽 서브트리를 중위 순회하여 그 노드들의 값을 출력합니다.
이러한 방식으로 중위 순회를 수행하면, BST의 모든 노드들이 키 값의 오름차 순으로 출력됩니다.
def inorder_tree_walk(self, x):
if x is not None:
self.inorder_tree_walk(x.left)
print(x.key, end=' ')
self.inorder_tree_walk(x.right)
transplant()
이진 탐색 트리에서 노드를 이동시키는데에 사용됩니다. 주로, 후계자를 이동시킬때 사용됩니다.
코드의 동작은 아래와 같습니다.
u
의 부모가 없는 경우(u.parent is None
)에는 u
가 루트 노드인 경우입니다. 이 경우에는 v
를 새로운 루트로 설정합니다(self.root = v
).u
가 부모의 왼쪽 자식인 경우(u == u.parent.left
)에는 v
를 u
의 부모의 왼쪽 자식으로 설정합니다(u.parent.left = v
).u
가 부모의 오른쪽 자식인 경우입니다. 이 경우에는 v
를 u
의 부모의 오른쪽 자식으로 설정합니다(u.parent.right = v
).v
가 None
이 아니라면, 즉 u
가 리프 노드가 아닌 경우에는 v
의 부모를 u
의 부모로 설정합니다(v.parent = u.parent
).def transplant(self, u, v):
if u.parent is None:
self.root = v
elif u == u.parent.left:
u.parent.left = v
else:
u.parent.right = v
if v is not None:
v.parent = u.parent
tree_delete()
BST의 삭제 연산은 다음과 같이 삭제할 노드의 자식의 갯수에 따라 세 가지 경우로 나누어집니다.
💡 **Case 0**: 삭제할 노드가 자식이 없는 경우Case 1: 삭제할 노드가 한 개의 자식을 가진 경우
Case 2: 삭제할 노드가 두 개의 자식을 가진 경우
각 케이스별로 설명해보겠습니다.
z
의 부모 노드 y
를 찾습니다.z
의 자식 노드 x
를 결정합니다. 자식이 하나인 경우에는 그 자식 노드가 x
가 됩니다.y
의 자식을 삭제할 노드 z
의 자식 노드 x
로 변경합니다. 이를 위해 부모 노드의 적절한 자식 포인터를 x
로 설정합니다.z
를 반환합니다.def tree_delete(self, z):
# 어떤 노드를 잘라내야 하는지 결정: z 또는 z의 후계자
if z.left is None or z.right is None:
y = z # case 0 or 1
else:
y = self.tree_successor(z) # case 2
# x를 y의 NIL이 아닌 자식으로 설정하거나, y가 자식이 없으면 NIL로 설정
if y.left is not None:
x = y.left
else:
x = y.right
# y가 트리에서 제거되며, p[y]와 x의 포인터를 조작함
if x is not None:
x.parent = y.parent
if y.parent is None:
self.root = x
elif y == y.parent.left:
y.parent.left = x
else:
y.parent.right = x
# z의 후계자가 잘라낸 경우, 해당 데이터를 z로 복사함
if y != z:
z.key = y.key
# 여기서 다른 위성 데이터도 필요한 경우 해당 데이터를 복사해야 합니다.
return y
print_tree()
, _print_tree_recursive
실습을 위해 tree를 출력하기 위한 함수를 아래와 같이 정의했습니다.
def print_tree(self):
self._print_tree_recursive(self.root, 0)
def _print_tree_recursive(self, node, level):
if node is None:
return
self._print_tree_recursive(node.right, level + 1)
print(' ' * level + str(node.key))
self._print_tree_recursive(node.left, level + 1)
# test
if __name__ == "__main__":
bst = BST()
nodes = [56, 26, 200, 18, 28, 190, 213, 12, 24, 27]
for key in nodes:
bst.tree_insert(Node(key))
print("Initial BST (in-order traversal):")
bst.inorder_tree_walk(bst.root)
print() # 줄바꿈
bst.print_tree()