트리 :
그래프 : 자식 노드에 여러 개의 부모 노드가 있는 구조
한 노드에서 시작해서 다른 정점들을 순회하여 자기 자신에게 돌아오는 순환이 없는 연결 그래프 (순환하는 길이 없는 그래프)이다.
"나무"라는 뜻이며, 정점 node 과 간선 edge 을 이용하여 데이터의 배치 형태를 추상화한 2차원 자료 구조이다. 데이터 검색과 탐색에 널리 이용된다.

루트 (root) 노드 : 부모가 없는 노드, 맨 위의 노드로, 단 하나만 존재
리프 (leaf) 노드 : 자식이 없는 노드 (= 말단 노드, 잎 노드)
내부 (internal) 노드 : root나 leaf가 아닌 노드
간선 (edge) : 노드를 연결하는 선 (= link, branch)
부분/하위 트리 (subtree) : 노드 하나와 그 자식 노드들로 구성된 트리
형제 (sibling) : 같은 부모를 가지는 노드
부모 (parant) 의 부모~ : 조상 (ancestor)
자식 (child) 의 자식~ : 후손 (descendant)
노드의 크기 (size) : 자신을 포함한 모든 자손 노드의 개수
노드의 깊이 (depth) : 루트에서 어떤 노드에 도달하기 위해 거쳐야 하는 간선의 수
노드의 레벨 (level) : 트리의 특정 깊이를 가지는 노드의 집합
노드의 차수 (degree) : 하위 트리 개수 = 간선 수 (degree) : 각 노드가 지닌 가지의 수
트리의 차수 (degree of tree) : 트리의 최대 차수
트리의 높이 (height) : 루트 노드에서 가장 깊숙히 있는 노드의 깊이
🖐️ 아래 내용들은 이진 트리를 참고하여 이해하면 쉽다.
연산 :
size() - 현재 트리에 포함되어 있는 노드의 수를 구함depth() - 현재 트리의 깊이(또는 높이; height)를 구함구현 :
노드 : data, left child, right child트리 : rootsize(), depth() : 재귀적인 방법으로 쉽게 구할 수 있다.class Node :
def __init__(self, item) :
self.data = item
self.left = None
self.right = None
def size(self) :
l = self.left.size() if self.left else 0
r = self.right.size() if self.right else 0
return l + r + 1 # 자기 자신 하나도 더해야 한다.
def depth(self) :
class BinaryTree :
def __init__(self, r) :
self.root = r
def size(self) :
if self.root :
return self.root.size()
else :
return 0
def depth(self) :
if
정해진 순서대로 노드를 방문해서 처리하는 연산 (트리를 탐색하는 과정)
깊이 우선 순회 (중위, 전위, 후위)와 넓이 우선 순회가 있다.

중위 순회 in-order traversal : 왼쪽 서브트리 (하위) → 자기 자신 → 오른쪽 서브트리 순회전위 순회 pre-order traversal : 자기 자신 → 왼쪽 서브트리 → 오른쪽 서브트리 순회후위 순회 post-order traversal : 왼쪽 서브트리 (하위) → 오른쪽 서브트리 순회 → 자기 자신class Node :
def inorder(self) :
traversal = []
if self.left :
traversal += self.left.inorder()
traversal.append(self.data)
if self.right :
traversal += self.right.inorder()
return traversal
class BinaryTree :
def inorder(self) :
if self.root :
return self.noot.inorder()
else :
return []
level 이 낮은 노드 우선 방문넓이 우선 순회 알고리즘 구현하기 :
초기화(traversal.append)q == empty : 모든 노드 방문 완료! return traversalclass Binary Tree :
def bft(self) :
노드 하나와 그 자식 노드들로 구성된 트리
모든 노드의 차수가 2 이하인 트리
빈 트리 (Empty tree)이거나 루트 노드 + 왼쪽 서브트리 (이진) + 오른쪽 서브트리 (이진)
왼쪽 자식은 부모보다 작고 오른쪽 자식은 부모보다 큰 이진 트리

정렬된 배열을 이용한 이진 탐색과 비교하여,
장점 : 데이터 원소의 추가, 삭제가 용이하다.
단점 : 공간 소요가 크다.
복잡도 : 항상 O(logn)의 탐색 복잡도를 갖지는 않는다.
이진 탐색 트리의 데이터 표현은 각 노드는 (key, value) 쌍으로 한다. 키를 통해서 데이터 탐색이 가능해진다.
insert(key, value) : 트리에 주어진 데이터 원소를 추가remove(key) : 특정 원소를 트리로부터 삭제lookup(key) : 특정 원소를 검색 (탐색)inorder() : 키의 순서대로 데이터 원소들을 나열min(), max() : 최소 키, 최대 키를 가지는 원소를 각각 탐색class Node :
def __init__(self, key, data) :
self.key = key
self.data = data
self.left = None
self.right = None
class BinSearchTree :
def __init__(self) :
self.root = None
class Node :
def inorder(self) :
traversal = []
if self.left :
traversal += self.left.inorder()
traversal.append(self)
if self.right :
traversal += self.right.inorder
return traversal
class BinSearchTree :
def inorder(self) :
if self.root :
return self.root.inorder()
else :
return []
class BinSearchTree :
def min(self) :
if self.left :
return.self.root.min()
else :
return self
def min(self) :
if self.root is None :
return None
p = self.root
while p.left is not None :
p = p.left
return p.key
def max(self) :
if self.right :
return.self.root.max()
else :
return self
def max(self) :
if self.root is None :
return None
p = self.root
while p.right is not None :
p = p.right
return p.key
class Node :
def insert(self, key, data) :
if key < self.key :
elif key > self.key :
else :
raise KeyError('...')
class BinSearchTree :
def insert(self, key, value) :
if self.root :
self.root.insert(key, data)
else :
self.root = Node(key, data)
class Node :
def lookup(self, key, parant=None) :
if key < self.key :
if self.left :
return self.left.lookup(key, self)
else :
return None, None
elif key > self.key :
if self.right :
return self.right.lookup(key, self)
else :
return None, None
else :
return self, parant
class BinSearchTree :
def lookup(self, key) : # 방법 1
if self.root :
return self.root.lookup(key)
else :
return None, None
def search(self, key) : # 방법 2
p = self.root # 루트에 주목
while True :
if p is None :
return None
if key == p.key:
return p.value
elif key < p.key :
p.left
else :
p = p.right
삭제되는 노드 X 가 leaf node인 경우 (leaf 노드 삭제)삭제되는 노드 X 가 자식을 하나 가지고 있는 경우 (자식이 하나인 노드 삭제)삭제되는 노드 X 가 자식을 둘 가지고 있는 경우 (자식이 둘인 노드 삭제)class BinSearchTree :
def remove(self, key) :
p = self.root # 스캔 중인 노드
parent = None
is_left_child = True # p는 parent의 왼쪽 자식 노드인지 확인
while True :
if p is None : return False # 키 존재하지 않음
if key == p.key : break # 검색 성공
else :
parent = p
if key < p.key :
is_left_child = True
p = p.left
else :
is_left_child = False
p = p.right
if p.left is None: # p에 왼쪽 자식이 없으면
if p is self.root:
self.root = p.right
elif is_left_child:
parent.left = p.right # 부모의 왼쪽 포인터가 오른쪽 자식을 가리킴
else:
parent.right = p.right # 부모의 오른쪽 포인터가 오른쪽 자식을 가리킴
elif p.right is None: # p에 오른쪽 자식이 없으면
if p is self.root:
self.root = p.left
elif is_left_child:
parent.left = p.left # 부모의 왼쪽 포인터가 왼쪽 자식을 가리킴
else:
parent.right = p.left # 부모의 오른쪽 포인터가 왼쪽 자식을 가리킴
else:
parent = p
left = p.left # 서브 트리 안에서 가장 큰 노드
is_left_child = True
while left.right is not None: # 가장 큰 노드 left를 검색
parent = left
left = left.right
is_left_child = False
p.key = left.key # left의 키를 p로 이동
p.value = left.value # left의 데이터를 p로 이동
if is_left_child:
parent.left = left.left # left를 삭제
else:
parent.right = left.left # left를 삭제
return True
class Node :
def __init__(self, key, data) :
self.key = key
self.data = data
self.left = None
self.right = None
def inorder(self) :
traversal = []
if self.left :
traversal += self.left.inorder()
traversal.append(self)
if self.right :
traversal += self.right.inorder
return traversal
def lookup(self, key, parant=None) :
if key < self.key :
if self.left :
return self.left.lookup(key, self)
else :
return None, None
elif key > self.key :
if self.right :
return self.right.lookup(key, self)
else :
return None, None
else :
return self, parant
def insert(self, key, data) :
if key < self.key :
elif key > self.key :
else :
raise KeyError('...')
def countChildren(self) :
# remove()를 위한 자식 노드 갯수 세는 코드
count = 0
if self.left :
count += 1
if self.right :
count += 1
return count
def remove(self, key) :
class BinSearchTree :
def __init__(self) :
self.root = None
def inorder(self) :
if self.root :
return self.root.inorder()
else :
return []
def min(self) :
if self.left :
return.self.root.min()
else :
return self
def max(self) :
if self.right :
return.self.root.max()
else :
return self
def lookup(self, key) :
if self.root :
return self.root.lookup(key)
else :
return None, None
def insert(self, key, value) :
if self.root :
self.root.insert(key, data)
else :
self.root = Node(key, data)
def remove(self, key) :
node, parent = self.lookup(key)
if node :
return True
else :
return False
한쪽으로 치우친 경우, 이진 탐색 트리의 장점을 발휘하지 못하고, 선형 탐색과 같은 효율을 갖게 된다.
비효율적인 단점을 해결하기 위해 높이의 균형을 유지함으로써 O(logn)의 탐색 복잡도를 보장할 수 있다.
하지만 그렇게 트리의 구조를 유지하려다 보면 기초 이진 트리에 비해 삽입, 삭제 연산이 보다 복잡하다.
AVL tree, Red-black tree가 이에 해당된다.
모든 레벨에서 노드들이 모두 채워져 있는 이진 트리
k이고 노드의 개수가 2^k - 1인 이진 트리높이가 k인 완전 이진 트리
k-2 까지는 모든 노드가 2개의 자식을 가진 포화 이진 트리k-1 에서는 왼쪽부터 노드가 순차적으로 채워져 있는 이진 트리불균형 이진 트리는 많은 노드가 단 하나의 자식 노드를 갖는 구조이다.
불균형 이진 트리에서 서브트리 2개 사이에서 높이 차이를 감지하여 트리 회전 (tree rotation, 균형 조정 과정) 수행한 후의 트리
자체적으로 균형을 조정한다는 점에서 AVL 이진 탐색 트리와 비슷하지만, 트리의 구조 때문에 균형을 조정하는 과정에서 트리 회전수가 적어 AVL 트리보다 효율적이다.
컴퓨터 과학자들이 데이터베이스 시스템을 설계할 때 사용하는 데이터 구조
자료구조 힙 (heaps)에 대해 더 알아보기
자료구조 스택 (Stacks)에 대해 더 알아보기
자료구조 큐 (Queues)에 대해 더 알아보기
자료구조 그래프 (Graph)에 대해 더 알아보기
출처
programmers, gmlwjd9405.github.io, velog.io/@dlgosla, dream-and-develop.tistory.com, velog.io/@vagabondms