Search
Duplicate

Tree

트리

1. 트리 (Tree) 구조

트리: Node와 Branch를 이용해서, 사이클을 이루지 않도록 구성한 데이터 구조
실제로 어디에 많이 사용되나?
트리 중 이진 트리 (Binary Tree) 형태의 구조로, 탐색(검색) 알고리즘 구현을 위해 많이 사용됨
Node: 트리에서 데이터를 저장하는 기본 요소 (데이터와 다른 연결된 노드에 대한 Branch 정보 포함)
Root Node: 트리 맨 위에 있는 노드
Level: 최상위 노드를 Level 0으로 하였을 때, 하위 Branch로 연결된 노드의 깊이를 나타냄
Parent Node: 어떤 노드의 다음 레벨에 연결된 노드
Child Node: 어떤 노드의 상위 레벨에 연결된 노드
Leaf Node (Terminal Node): Child Node가 하나도 없는 노드
Sibling (Brother Node): 동일한 Parent Node를 가진 노드
Depth: 트리에서 Node가 가질 수 있는 최대 Level

2. 이진 트리와 이진 탐색 트리 (Binary Search Tree)

이진 트리: 노드의 최대 Branch가 2인 트리
이진 탐색 트리 (Binary Search Tree, BST): 이진 트리에 다음과 같은 추가적인 조건이 있는 트리
왼쪽 노드는 해당 노드보다 작은 값, 오른쪽 노드는 해당 노드보다 큰 값을 가지고 있음!

3. 자료 구조 이진 탐색 트리의 장점과 주요 용도

주요 용도: 데이터 검색(탐색)
장점: 탐색 속도를 개선할 수 있음
단점: 입력이 balanced하게 들어오지 않을 경우에는 $ O(n)$

이진트리와 정렬된 배열간의 탐색 비교

4. 이진 탐색 트리의 시간 복잡도와 단점

4.1. 시간 복잡도 (탐색시)

depth (트리의 높이) 를 h라고 표기한다면, O(h)
n개의 노드를 가진다면, $h = log_2{n} $ 에 가까우므로, 시간 복잡도는 $ O(log{n}) $
참고: 빅오 표기법에서 $log{n}$ 에서의 log의 밑은 10이 아니라, 2.
한번 실행시마다, 50%의 실행할 수도 있는 명령을 제거한다는 의미. 즉 50%의 실행시간을 단축시킬 수 있다는 것을 의미함

5. 이진 탐색 트리 삭제

매우 복잡함. 경우를 나누어서 이해하는 것이 좋음

5.1. Leaf Node 삭제

Leaf Node: Child Node 가 없는 Node
삭제할 Node의 Parent Node가 삭제할 Node를 가리키지 않도록 한다.

5.2. Child Node 가 하나인 Node 삭제

삭제할 Node의 Parent Node가 삭제할 Node의 Child Node를 가리키도록 한다.

5.3. Child Node 가 두 개인 Node 삭제

1.
삭제할 Node의 오른쪽 자식 중, 가장 작은 값을 삭제할 Node의 Parent Node가 가리키도록 한다.
2.
삭제할 Node의 왼쪽 자식 중, 가장 큰 값을 삭제할 Node의 Parent Node가 가리키도록 한다.

5.3.1. 삭제할 Node의 오른쪽 자식중, 가장 작은 값을 삭제할 Node의 Parent Node가 가리키게 할 경우

경우의 수가 또다시 두가지가 있음
Case5-1-1: 삭제할 Node가 Parent Node의 왼쪽에 있고, 삭제할 Node의 오른쪽 자식 중, 가장 작은 값을 가진 Node의 Child Node가 없을 때
Case5-1-2: 삭제할 Node가 Parent Node의 왼쪽에 있고, 삭제할 Node의 오른쪽 자식 중, 가장 작은 값을 가진 Node의 오른쪽에 Child Node가 있을 때
가장 작은 값을 가진 Node의 Child Node가 왼쪽에 있을 경우는 없음, 왜냐하면 왼쪽 Node가 있다는 것은 해당 Node보다 더 작은 값을 가진 Node가 있다는 뜻이기 때문임
Case5-2-1: 삭제할 Node가 Parent Node의 오른쪽에 있고, 삭제할 Node의 오른쪽 자식 중, 가장 작은 값을 가진 Node의 Child Node가 없을 때
Case5-2-2: 삭제할 Node가 Parent Node의 오른쪽에 있고, 삭제할 Node의 오른쪽 자식 중, 가장 작은 값을 가진 Node의 오른쪽에 Child Node가 있을 때
가장 작은 값을 가진 Node의 Child Node가 왼쪽에 있을 경우는 없음, 왜냐하면 왼쪽 Node가 있다는 것은 해당 Node보다 더 작은 값을 가진 Node가 있다는 뜻이기 때문임

6.1. 구현

class Node: def __init__(self, value): self.value = value self.left = None self.right = None class NodeMgmt: def __init__(self, head): self.head = head def insert(self, value): self.current_node = self.head while True: if value < self.current_node.value: if self.current_node.left != None: self.current_node = self.current_node.left else: self.current_node.left = Node(value) break else: if self.current_node.right != None: self.current_node = self.current_node.right else: self.current_node.right = Node(value) break def search(self, value): self.current_node = self.head while self.current_node: if self.current_node.value == value: return True elif value < self.current_node.value: self.current_node = self.current_node.left else: self.current_node = self.current_node.right return False def delete(self, value): # 삭제할 노드 탐색 searched = False self.current_node = self.head self.parent = self.head while self.current_node: if self.current_node.value == value: searched = True break elif value < self.current_node.value: self.parent = self.current_node self.current_node = self.current_node.left else: self.parent = self.current_node self.current_node = self.current_node.right if searched == False: return False # case1 삭제할 Node가 Leaf Node인 경우 if self.current_node.left == None and self.current_node.right == None: if value < self.parent.value: self.parent.left = None else: self.parent.right = None # case2 삭제할 Node가 Child를 하나만 가지고 있을 경우 -> 부모 기준 좌우를 or 삭제할 Node의 왼오 먼저하는 방법 둘다 가능 elif self.current_node.left != None and self.current_node.right == None: #삭제할 Node의 왼쪽 자식이 있는 경우 if value < self.parent.value: #삭제할 Node가 부모의 왼쪽 자식일 때 self.parent.left = self.current_node.left #부모의 left에 삭제할 Node의 자식 붙여줌 else: #삭제할 Node가 부모의 오른쪽 자식일 때 self.parent.right = self.current_node.left #부모의 right에 삭제할 Node의 자식 붙여줌 elif self.current_node.left == None and self.current_node.right != None: #삭제할 Node의 오른쪽 자식이 있는 경우 if value < self.parent.value: #삭제할 Node가 부모의 왼쪽 자식일 때 self.parent.left = self.current_node.right else: self.parent.right = self.current_node.right # case 3 elif self.current_node.left != None and self.current_node.right != None: # case 3-1 if value < self.parent.value: self.change_node = self.current_node.right self.change_node_parent = self.current_node.right while self.change_node.left != None: #계속 왼쪽 자식 찾기 self.change_node_parent = self.change_node self.change_node = self.change_node.left if self.change_node.right != None: #대체할 Node의 오른쪽 자식이 있으면 self.change_node_parent.left = self.change_node.right #대체할 Node의 부모의 left에 연결해줌 else: self.change_node_parent.left = None self.parent.left = self.change_node #삭제할 Node의 부모의 Left에 대체할 Node를 연결 self.change_node.right = self.current_node.right #삭제할 Node가 기존에 가지고 있던 right,left 정보를 대체 Node로 self.change_node.left = self.change_node.left # case 3-2 else: self.change_node = self.current_node.right self.change_node_parent = self.current_node.right while self.change_node.left != None: self.change_node_parent = self.change_node self.change_node = self.change_node.left if self.change_node.right != None: self.change_node_parent.left = self.change_node.right else: self.change_node_parent.left = None self.parent.right = self.change_node self.change_node.right = self.current_node.right self.change_node.left = self.current_node.left return True
Plain Text
복사

6.2. 테스트

random 라이브러리 활용
random.randint(첫번째 숫자, 마지막 숫자): 첫번째 숫자부터 마지막 숫자 사이에 있는 숫자를 랜덤하게 선택해서 리턴
예: random.randint(0, 99): 0에서 99까지 숫자중 특정 숫자를 랜덤하게 선택해서 리턴해줌
# 0 ~ 999 숫자 중에서 임의로 100개를 추출해서, 이진 탐색 트리에 입력, 검색, 삭제 import random # 0 ~ 999 중, 100 개의 숫자 랜덤 선택 bst_nums = set() while len(bst_nums) != 100: bst_nums.add(random.randint(0, 999)) # print (bst_nums) # 선택된 100개의 숫자를 이진 탐색 트리에 입력, 임의로 루트노드는 500을 넣기로 함 head = Node(500) binary_tree = NodeMgmt(head) for num in bst_nums: binary_tree.insert(num) # 입력한 100개의 숫자 검색 (검색 기능 확인) for num in bst_nums: if binary_tree.search(num) == False: print ('search failed', num) # 입력한 100개의 숫자 중 10개의 숫자를 랜덤 선택 delete_nums = set() bst_nums = list(bst_nums) while len(delete_nums) != 10: delete_nums.add(bst_nums[random.randint(0, 99)]) # 선택한 10개의 숫자를 삭제 (삭제 기능 확인) for del_num in delete_nums: if binary_tree.delete(del_num) == False: print('delete failed', del_num)
Plain Text
복사