"이 글은 골든래빗 코딩 테스트 합격자 되기 파이썬 편 9장의 써머리입니다."
트리(Tree) : 데이터를 저장하고 탐색하기에 유용한 구조를 가짐
트리의 특성을 활용하는 분야
계층 구조를 표현하는 용도로 많이 사용(eg. 파일 시스템, 디렉터리 구조)
나무를 거꾸로 뒤집어 놓은 모양의 트리

트리를 구성하는 노드
루트 노드(Root Node) : 노드 중 가장 위에 있는 노드
노드를 연결하는 에지
간선 or 에지(Edge) : 노드와 노드 사이에 이어주는 선
레벨 : 루트 노드로부터 특정 노드까지 거쳐가는 최소한의 간선 수
부모-자식, 형제 관계를 가지는 노드
부모-자식 관계 : 간선으로 연결된 노드들
형제 노드(Sibling Node) : 같은 부모를 갖는 노드
자식이 없는 말단 노드
리프 노드(Leaf Node) : 자식이 없는 노드
아래로 향하는 간선의 개수, 차수
차수(Degree) : 특정 노드에서 아래로 향하는 간선의 개수
이진 트리(Binary Tree) : 모든 노드의 최대 차수가 2를 넘지 않는 트리
배열로 표현하기
배열 - 선형 자료구조 / 트리 - 계층 자료구조
3가지 규칙 필요
1) 루트 노드는 배열 인덱스 1번에 저장
2) 왼쪽 자식 노드의 배열 인덱스 : 부모 노드의 배열 인덱스 x 2
3) 오른쪽 자식 노드의 배열 인덱스 : 부모 노드의 배열 인덱스 x 2 + 1
* 메모리만 넉넉하다면 구현 시간을 단축하는 용도로 Good
이진 트리 순회하기
순회 : 어떤 데이터가 있을 때 그 데이터를 빠짐 없이 방문하는 것을 의미
→ 트리에서 데이터를 검색하려면 트리를 순회할 수 있어야 함
트리 순회 방법
* 방문 : 탐색에서 방문은 탐색을 마친 상태를 의미, 탐색 과정에서 지나치는 것과 그렇지 않은 것을 구분하기 위해 방문이라는 용어를 사용
전위 순회 과정 살펴보기
01단계) 루트 노드에서 시작
02단계) 현재 노드를 부모로 생각, 다음 왼쪽 자식 노드로 이동
03 단계) 오른쪽 자식 노드 방문
04 단계) 오른쪽 자식 노드가 없을 경우, 현재 노드의 부모 노드로 이동
* 전위 순회는 주로 트리를 복사할 때 많이 사용
중위 순회 과정 살펴보기
01단계) 루트 노드에서 시작 → 왼쪽 자식 노드 이동
02단계) 01단계의 노드를 부모로, 이 노드의 왼쪽 자식 노드 방문/이동
03단계) 부모 노드 방문
04단계) 오른쪽 자식 노드 방문 후 거슬러 올라감
* 중위 순회는 이진 탐색 트리에서 정렬된 순서대로 값을 가져올 때 사용
후위 순회 과정 살펴보기
01단계) 루트 노트에서 시작 → 왼쪽 자식 노드로 이동
02단계) 왼쪽 노드의 자식이 없을 때까지 이동 후 자신 방문
03단계) 오른쪽 노드 방문 후 거슬러 올라가서 부모 노드 방문
04단계) 왼쪽 트리 전체 완료 후 오른쪽 트리 시작할 때도 같은 방식
노드 삭제
* 자식 노드부터 방문한다는 특성이 있는 후위 순회는 트리 삭제에 자주 활용
포인터로 표현하기
포인터로 트리 표현 시 노드부터 정의 필요

노드는 노드의 값, 왼쪽 자식 노드와 오른쪽 자식 노드를 가짐
포인터로 표현한 트리는 배열과 달리 인덱스 연산을 하지 않으므로, 메모리 공간 낭비 X
이진 트리에서 가장 중요한 부분 : 탐색을 효율적으로 할 수 있도록 트리 구축하는 것
이진 탐색 트리(Binary Search Tree) 구축하기
이진 탐색 트리 정렬 방식
: 데이터 크기를 따져 크기가 작으면 왼쪽 자식 위치에, 크거나 같으면 오른쪽 자식 위치에 배치
이진 탐색 트리 탐색하기
1) 찾으려는 값이 현재 노드의 값과 같으면 탐색을 종료하고 크면 오른쪽 노드 탐색
2) 본인이 찾으려는 값이 현재 노드의 값보다 작으면 왼쪽 노드 탐색
3) 값을 찾으면 종료. 노드가 없을 때까지 계속 탐색했는데 값이 없으면 현재 트리에 값이 없는 것
이진 탐색 트리는 크면 오른쪽, 작으면 왼쪽
모든 탐색 알고리즘에서 탐색 효율을 개선하는 방법은 같음 - 탐색 대상이 아닌 노드를 한 번에 많이 제외할 수 있으면 됨
데이터 크기에 따라 하위 데이터 중 한 방향을 검색 대상에서 제외 - 검색 빨라짐
이진 탐색 트리의 시간 복잡도
이진 탐색 트리의 시간 복잡도는 트리 균형에 의존. 즉, 각 노드의 차수가 비슷하게 유지되면서 각 노드의 자식 노드 수가 비슷하게 유지되는 것을 의미
⇒ 트리에 저장된 노드가 N개 일 때 시간 복잡도는 O(logN)
균형 이진 탐색 트리(Balanced Binary Search Tree)
한 쪽으로만 치우쳐지지 않도록 균형을 유지하는 이진 탐색 트리
세부적으로 AVL 트리, 레드-블랙 트리 등으로 구분
이진 트리의 탐색 연산 횟수가 트리 높이에 비례하고 트리의 높이는 logN이므로 탐색 시간 복잡도를 O(logN)으로 유지 가능, But 구현 난이도가 높음
문제 26) 트리 순회
이진 트리를 표현한 리스트 nodes를 인자로 받아 트리를 표현한 것. 해당 이진 트리에 대해 전위 순회, 중위 순회, 후위 순회 결과를 반환하는 함수 구현
def preorder(nodes, idx):
res = []
if idx < len(nodes):
res.append(nodes[idx])
res.extend(preorder(nodes, 2 * idx + 1 ))
res.extend(preorder(nodes, 2 * idx + 2))
return res
def inorder(nodes, idx):
res = []
if idx < len(nodes):
res.extend(inorder(nodes, 2 * idx + 1))
res.append(nodes[idx])
res.extend(inorder(nodes, 2 * idx + 2))
return res
def postorder(nodes, idx):
res = []
if idx < len(nodes):
res.extend(postorder(nodes, 2 * idx + 1))
res.extend(postorder(nodes, 2 * idx + 2))
res.append(nodes[idx])
return res
def solution(nodes):
result = [
preorder(nodes, 0),
inorder(nodes, 0),
postorder(nodes, 0)
]
return result
문제 27) 이진 탐색 트리 구현
첫 번째 인수 lst를 이용하여 이진 탐색 트리 생성, 두 번째 인수 search_lst에 있는 각 노드를 이진 탐색 트리에서 찾을 수 있는지 확인하여 True or False를 담은 리스트 result를 반환하는 함수 작성
class Node:
def __init__(self, item):
self.left = None
self.right = None
self.item = item
class BinaryTree:
def __init__(self):
self.root = None
def insert(self, key):
self.root = self._insert(self.root, key)
def _insert(self, root, key):
if root is None:
return Node(key)
if key < root.item:
root.left = self._insert(root.left, key)
else:
root.right = self._insert(root.right, key)
return root
def search(self, root, value):
if root is None or root.item == value:
return root is not None
if value < root.item:
return self.search(root.left, value)
else:
return self.search(root.right, value)
def solution(lst, search_lst):
tree = BinaryTree()
for value in lst:
tree.insert(value)
result = [tree.search(tree.root, value) for value in search_lst]
return result
문제 28) 예상 대진표
게임 참가자 수 N, 참가자 번호 A, 경쟁자 번호 B가 함수 solution()의 인수로 주어질 때 첫 라운드에서 A번을 가진 참가자는 경쟁자로 생각하는 B번 참가자와 몇 번째 라운드에서 만나는 지 반환
def solution(n,a,b):
answer = 0
while a != b:
a = (a + 1) // 2
b = (b + 1) // 2
answer += 1
return answer