특정 노드를 봤을 때, 왼쪽 부분 트리에 있는 모든 노드는 그 노드의 데이터보다 작아야 함
반대로 오른쪽 부분 트리에 있는 모든 노드는 그 노드의 데이터보다 커야 함
➡ 이진 탐색 트리 노드 구현
힙은 항상 완전 이진 트리기 때문에 배열로 구현
이진 탐색 트리는 이진 트리지만 완전 이진 트리라는 보장은 없음
노드 클래스를 정의한 후 여러 노드 인스턴스들을 생성하고 인스턴스들을 연결시켜 구현
노드들을 어떻게 연결하고, 어떻게 삭제하고, 어떻게 찾는지가 핵심
이진 탐색 트리에서 in-order 순회 함수를 쓰면 정렬된 순서대로 데이터 출력 가능
defprint_inorder(node):"""주어진 노드를 in-order로 출력해주는 함수"""if node isnotNone:
print_inorder(node.left_child)print(node.data)
print_inorder(node.right_child)classNode:"""이진 탐색 트리 노드 클래스"""def__init__(self, data):
self.data = data
self.parent =None
self.left_child =None
self.right_child =NoneclassBinarySearchTree:"""이진 탐색 트리 클래스"""def__init__(self):
self.root =Nonedefprint_sorted_tree(self):"""이진 탐색 트리 내의 데이터를 정렬된 순서로 출력해주는 메소드"""
print_inorder(self.root)# 비어 있는 이진 탐색 트리 생성
bst = BinarySearchTree()
🙄 이진 탐색 트리 연산
➡ 이진 탐색 트리 삽입
삽입 이후에도 이진 탐색 트리 속성이 유지되어야 함
새로운 노드 생성
root 노드부터 비교하면서 저장할 위치 찾음
찾은 위치에 새롭게 만든 노드 연결
시간 복잡도
트리의 높이 : h
: O(1)
: O(h)
: O(1)
시간 복잡도 : O(1+h+1) = O(h)
classBinarySearchTree:"""이진 탐색 트리 클래스"""def__init__(self):
self.root =Nonedefinsert(self, data):
new_node = Node(data)# 삽입할 데이터를 갖는 새 노드 생성# 트리가 비었으면 새로운 노드를 root 노드로 만든다if self.root isNone:
self.root = new_node
return
temp = self.root
while temp isnotNone:if data > temp.data:if temp.right_child isNone:
temp.right_child = new_node
new_node.parent = temp
returnelse:
temp = temp.right_child
else:if temp.left_child isNone:
temp.left_child = new_node
new_node.parent = temp
returnelse:
temp = temp.left_child
defprint_sorted_tree(self):"""이진 탐색 트리 내의 데이터를 정렬된 순서로 출력해주는 메소드"""
print_inorder(self.root)# root 노드를 in-order로 출력한다# 빈 이진 탐색 트리 생성
bst = BinarySearchTree()# 데이터 삽입
bst.insert(7)
bst.insert(11)
bst.insert(9)
bst.insert(17)
bst.insert(8)
bst.insert(5)
bst.insert(19)
bst.insert(3)
bst.insert(2)
bst.insert(4)
bst.insert(14)# 이진 탐색 트리 출력
bst.print_sorted_tree()# 2# 3# 4# 5# 7# 8# 9# 11# 14# 17# 19
➡ 이진 탐색 트리 탐색
특정 데이터를 갖는 노드를 리턴하는 연산
주어진 노드의 데이터와 탐색하려는 데이터 비교
탐색하려는 데이터가 더 크면 노드의 오른쪽 자식으로 간다
탐색하려는 데이터가 더 작으면 노드의 왼쪽 자식으로 간다
탐색하려는 노드를 찾으면 리턴한다
시간 복잡도
트리의 높이 : h
최악의 경우 : h+1번 탐색
시간 복잡도 : O(h+1) = O(h)
classBinarySearchTree:"""이진 탐색 트리 클래스"""def__init__(self):
self.root =Nonedefsearch(self, data):"""이진 탐색 트리 탐색 메소드, 찾는 데이터를 갖는 노드가 없으면 None을 리턴한다"""
temp = self.root
if data == temp.data:return temp
while temp isnotNone:if temp.data == data:return temp
elif data > temp.data:
temp = temp.right_child
else:
temp = temp.left_child
returnNone# 빈 이진 탐색 트리 생성
bst = BinarySearchTree()# 데이터 삽입
bst.insert(7)
bst.insert(11)
bst.insert(9)
bst.insert(17)
bst.insert(8)
bst.insert(5)
bst.insert(19)
bst.insert(3)
bst.insert(2)
bst.insert(4)
bst.insert(14)# 노드 탐색과 출력print(bst.search(7).data)print(bst.search(19).data)print(bst.search(2).data)print(bst.search(20))# 7# 19# 2# None
➡ 이진 탐색 트리 삭제
삭제하려는 데이터를 갖는 노드를 먼저 찾아야 됨
삭제하려는 데이터가 leaf 노드의 데이터일 때
부모 노드의 자식 레퍼런스를 None으로 지정해 줌
classBinarySearchTree:"""이진 탐색 트리 클래스"""def__init__(self):
self.root =Nonedefdelete(self, data):"""이진 탐색 트리 삭제 메소드"""
node_to_delete = self.search(data)# 삭제할 노드를 가지고 온다
parent_node = node_to_delete.parent # 삭제할 노드의 부모 노드# 경우 1: 지우려는 노드가 leaf 노드일 때 if node_to_delete.left_child isNoneand node_to_delete.right_child isNone:if node_to_delete == self.root:
self.root =Noneelse:if node_to_delete is parent_node.left_child:
parent_node.left_child =Noneelse:
parent_node.right_child =None
삭제하려는 데이터 노드가 하나의 자식 노드만 있을 때
자식 노드가 부모 노드의 자리를 차지하면 됨
classBinarySearchTree:"""이진 탐색 트리 클래스"""def__init__(self):
self.root =Nonedefdelete(self, data):"""이진 탐색 트리 삭제 메소드"""
node_to_delete = self.search(data)# 삭제할 노드를 가지고 온다
parent_node = node_to_delete.parent # 삭제할 노드의 부모 노드# 경우 1: 지우려는 노드가 leaf 노드일 때if node_to_delete.left_child isNoneand node_to_delete.right_child isNone:if self.root is node_to_delete:
self.root =Noneelse:# 일반적인 경우if node_to_delete is parent_node.left_child:
parent_node.left_child =Noneelse:
parent_node.right_child =None# 경우 2: 지우려는 노드가 자식이 하나인 노드일 때:elif node_to_delete.left_child or node_to_delete.right_child isNone:if node_to_delete.left_child isNone:# 오른쪽 자식이 하나라면if node_to_delete is self.root:# 지우려는게 root일 때
node_to_delete.right_child = self.root
else:# 지우려는게 부모의 왼쪽 자식이라면if node_to_delete is parent_node.left_child:
parent_node.left_child = node_to_delete.right_child
node_to_delete.right_child.parent = parent_node
else:# 지우려는게 부모의 오른쪽 자식이라면
parent_node.right_child = node_to_delete.right_child
node_to_delete.right_child.parent = parent_node
else:# 왼쪽 자식이 하나라면if node_to_delete is self.root:# 지우려는게 root일 때
node_to_delete.left_child = self.root
else:# 지우려는게 부모의 왼쪽 자식이라면if node_to_delete is parent_node.left_child:
parent_node.left_child = node_to_delete.left_child
node_to_delete.left_child.parent = parent_node
else:# 지우려는게 부모의 오른쪽 자식이라면
parent_node.right_child = node_to_delete.left_child
node_to_delete.left_child.parent = parent_node
삭제하려는 데이터 노드가 두 개의 자식이 있을 때
지우려는 노드의 오른쪽 자식 노드를 root로 갖는 부분 트리에서 가장 작은 데이터 노드로 대체한다
classBinarySearchTree:"""이진 탐색 트리 클래스"""def__init__(self):
self.root =Nonedefdelete(self, data):"""이진 탐색 트리 삭제 메소드"""
node_to_delete = self.search(data)# 삭제할 노드를 가지고 온다
parent_node = node_to_delete.parent # 삭제할 노드의 부모 노드# 경우 1: 지우려는 노드가 leaf 노드일 때if node_to_delete.left_child isNoneand node_to_delete.right_child isNone:if self.root is node_to_delete:
self.root =Noneelse:# 일반적인 경우if node_to_delete is parent_node.left_child:
parent_node.left_child =Noneelse:
parent_node.right_child =None# 경우 2: 지우려는 노드가 자식이 하나인 노드일 때:elif node_to_delete.left_child isNone:# 지우려는 노드가 오른쪽 자식만 있을 때:# 지우려는 노드가 root 노드일 때if node_to_delete is self.root:
self.root = node_to_delete.right_child
self.root.parent =None# 지우려는 노드가 부모의 왼쪽 자식일 때elif node_to_delete is parent_node.left_child:
parent_node.left_child = node_to_delete.right_child
node_to_delete.right_child.parent = parent_node
# 지우려는 노드가 부모의 오른쪽 자식일 때else:
parent_node.right_child = node_to_delete.right_child
node_to_delete.right_child.parent = parent_node
elif node_to_delete.right_child isNone:# 지우려는 노드가 왼쪽 자식만 있을 때:# 지우려는 노드가 root 노드일 때if node_to_delete is self.root:
self.root = node_to_delete.left_child
self.root.parent =None# 지우려는 노드가 부모의 왼쪽 자식일 때elif node_to_delete is parent_node.left_child:
parent_node.left_child = node_to_delete.left_child
node_to_delete.left_child.parent = parent_node
# 지우려는 노드가 부모의 오른쪽 자식일 때else:
parent_node.right_child = node_to_delete.left_child
node_to_delete.left_child.parent = parent_node
# 경우 3: 지우려는 노드가 2개의 자식이 있을 때else:
successor = self.find_min(node_to_delete.right_child)
successor_of_parent = successor.parent
node_to_delete.data = successor.data
# successor 노드 트리에서 삭제if successor.right_child isnotNone:# successor노드가 오른쪽 자식이 있을 때
successor.right_child.right_child = node_to_delete.right_child
if successor is successor_of_parent.left_child:# successor 노드가 왼쪽 자식일 때
successor_of_parent.left_child =Noneelse:# successor 노드가 오른쪽 자식일 때
successor_of_parent.right_child =None
@staticmethoddeffind_min(node):"""(부분)이진 탐색 트리의 가장 작은 노드 리턴"""# 코드를 쓰세요
temp = node # 탐색 변수. 파라미터 node로 초기화# temp가 node를 뿌리로 갖는 부분 트리에서 가장 작은 노드일 때까지 왼쪽 자식 노드로 간다while temp.left_child isnotNone:
temp = temp.left_child
return temp
시간 복잡도
지우려는 데이터 노드가 leaf 노드일 때
지우려는 데이터 노드가 하나의 자식이 있을 때
지우려는 데이터 노드가 두 개의 자식이 있을 때
탐색
탐색 후 단계들
경우 1
O(h)
O(1)
경우 2
O(h)
O(1)
경우 3
O(h)
O(h)
탐색 + 탐색 후 단계들
경우 1
O(h)
경우 2
O(h)
경우 3
O(h)
➡ 이진 탐색 트리 높이
트리의 높이가 h라고 할 때, 기본적인 연산들 (탐색, 삽입, 삭제)의 시간 복잡도는 O(h)
이진 탐색 트리 연산들의 시간은 모두 h에 비례
노드들이 직선적인 모양으로 저장될수록 비효율적
최악의 경우 : 모든 노드들이 직선 형태 : O(n)
균형 잡힌 완전 이진 트리에 가까울수록 효율적
최선의 경우 : 완전 이진 트리 : O(lg(n))