자료구조 - 트리, 이진 트리, 이진 탐색 트리
컴퓨터 사이언스 부트캠프 with 파이썬을 보고 정리한 내용입니다.
- 트리의 정의: 사이클이 없는 연결된 그래프
이진 트리
- 이진 트리란?
- 한 노드가 자식 노드를 두 개 이하만 갖는 트리
- 연결 리스트와 비슷함, 단 왼쪽 자식 노드와 오른쪽 자식 노드 2개를 참조해야 함
- 부모 노드와 자식 노드
- 루프 노드와 리프 노드
- 서브 트리
- 트리의 레벨
- 한 노드가 자식 노드를 두 개 이하만 갖는 트리
이진 트리의 종류
- 포화 이진 트리(full binary tree)
- 모든 레벨이 꽉 차 있는 트리
- 완전 이진 트리(complete binary tree)
- 트리의 노드가 위에서 아래로, 왼쪽에서 오른쪽으로 채워지는 트리
트리의 순회
- 전위 순회(preorder traversal)
- 노드 → 왼쪽 서브 트리 → 오른쪽 서브 트리 (재귀적으로 모든 서브 트리에 적용)
- 중위 순회(inorder traversal)
- 왼쪽 서브 트리 → 노드 → 오른쪽 서브 트리 (재귀적으로 모든 서브 트리에 적용)
- 후위 순회(postorder traversal)
- 왼쪽 서브 트리 → 오른쪽 서브 트리 → 노드 (재귀적으로 모든 서브 트리에 적용)
이진 트리 구현
- 트리 노드 구현
- 트리의 세 멤버 data, left, right를 캡슐화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class TreeNode:
def __init__(self):
self.__data = None
self.__left = None
self.__right = None
#노드 삭제를 확인하기 위한 소멸자
#객체가 삭제되기 전에 호출됨
def __del__(self):
print("TreeNode of {} is deleted".format(self.data))
@property
def data(self):
return self.__data
@left.setter
def left(self):
return self.__left
@right.setter
def right(self):
return self.__right
@right.setter
def right(self, right):
slef.__right = right
- 노드 관련 메서드 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class BinaryTree:
def __init__(self):
#멤버: 루트 노드를 가리키는 root 하나
self.root = None
def get_root(self):
return self.root
def set_root(self, r):
self.root = r
#새로우 노드를 만들어 반환
def make_node(self):
new_node = TreeNode()
return new_node
#노드 데이터 반환
def get_node_data(self, cur):
return cur.get_data()
#노드 데이터 설정
def set_node_data(self, cur, data):
cur.set_data(data)
###서브트리 관련 메서드
def get_left_sub_tree(self, cur):
return cur.left
def get_right_sub_tree(self,cur):
return cur.right
def make_left_sub_tree(self, cur, left):
cur.left = left
def make_right_sub_tree(slef, cur, right):
cur.right = right
### 순회
def preorder_traverse(self, cur, func):
#탈출조건: 방문한 노드가 빈 노드일 때
if not cur:
return
func(cur.data)
self.preorder_traverse(cur.left, func)
self.preporder_traverse(cur.right, func)
def inorder_traverse(self, cur, func):
#탈출조건: 방문한 노드가 빈 노드일 때
if not cur:
return
#먼저 왼쪽 서브트리 순회
self.inorder_traverse(cur.left, func)
func(cur.data)
self.inorder_traverse(cur.right, func)
def postorder_traverse(self, cur, func):
#탈출조건: 방문한 노드가 빈 노드일 때
if not cur:
return
self.postorder_traverse(cur.left, func)
self.postorder_traverse(cur.right, func)
#왼쪽 서브트리와 오른쪽 서브트리 순회 후 마지막으로 방문 노드의 데이터를 인자로 함수 호출
func(cur.data)
이진 탐색 트리
- 이진 탐색 트리(Bianry search tree)의 특징
- 어떤 특정 노드를 선택했을 때 그 노드를 기준으로 왼쪽 서브 트리에 존재하는 노드의 모든 데이터는 기준 노드의 값보다 작고, 오른쪽 서브 트리에 있는 노드의 모든 데이터는 기준 노드의 값보다 크다.
- 중복되는 값이 존재할 수 없다.(1번 조건으로 인해 당연)
이진 탐색 트리 구현
- 추상 자료형 (ADT)
BST.insert(data) -> None
- 데이터를 삽입
BST.search(target) -> node
- 대상 데이터를 가진 노드를 찾아서 반환, 없으면 None
BST.remove(target) -> node
- 대상 데이터가 있다면 삭제하면서 반환, 없으면 None
BST.insert_node(node) - > None
- 데이터가 아니라 노드를 삽입 (remove에서 반환받은 노드의 데이터를 수정한 후 다시 삽입할 때 사용)
- 이진 트리 관련 메서드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from binary_tree import *
class BST:
def __init__(self):
self.root = None
def get_root(self):
return self.root
def preorder_traverse(self, cur, f):
if not cur:
return
f(cur.data)
self.preorder_traverse(cur.left, f)
self.preorder_traverse(cur.right, f)
- insert
- 삽입 데이터가 노드의 데이터보다 작으면 왼쪽 자식 노드로 이동, 노드의 데이터보다 크면 오른쪽 자식 노드로 이동 → 빈 노드를 만나면 삽입한다!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def insert(self, data):
new_node = TreeNode()
new_node.data = data
cur = self.root
#루트 노드가 없을 때
if cur == None:
slef.root = new_node
return
while True:
#parent: 현재 순회 중인 노드의 부모 노드를 가리킴
parent = cur
if data < cur.data:
cur = cur.left
#왼쪽 서브트리가 없으면 거기에 새 노드를
if not cur:
parent.left = new_node
return
else:
cur = cur.right
#오른쪽 서브트리가 없으면 거기에 새 노드를
if not cur:
parent.right = new_node
return
- search
1
2
3
4
5
6
7
8
9
10
11
12
def search(self, target):
cur = self.root
while cur:
if target == cur.data:
return cur
#대상 데이터가 노드 데이터보다 적으면 왼쪽 자식 노드로 이동, 크면 오른쪽
elif target < cur.data:
cur = cur.left
elif target > cur.data:
cur = cur.right
return cur
- remove
- 유저 프로그래머가 사용하는 코드
1
2
3
4
5
6
7
8
def remove(self, target):
#루트 노드의 변경 가능성이 있으므로 루트를 업데이트해야 함
self.root, removed_node = self.__remove_recursion(self.root, target)
#삭제된 노드의 자식 노드를 None으로 만든다
removed_node.left = removed_node.right = None
#노드 데이터를 수정한 뒤 다시 삽입하기 위해서 삭제된 노드를 반환함
return removed_node
- 재귀함수를 사용해 구현, 세개의 경우로 나눠서 지움
- 삭제 노드가 리프 노드일 때
- 삭제 노드의 자식 노드가 하나일 때
- 삭제 노드의 자식 노드가 두개일 때
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def __remove_recursion(self, cur, target):
#탈출 조건 1: 대상 데이터가 트리 안에 없을 때
if cur == None:
return None, None
#대상 데이터가 노드 데이터보다 작으면, 노드의 왼쪽 자식에서 대상 데이터를 가진 노드를 지운다(재귀)
elif target < cur.data:
cur.left, rem_node = self.__remove_recursion(cur.left, target)
#대상 데이터가 노드 데이터보다 크면, 노드의 오른쪽 자식에서 대상 데이터를 가진 노드를 지운다(재귀)
elif target > cur.data:
cur.right, rem_node = self.__remove_recursion(cur.right, target)
#탈출 조건 2: target == cur.data
else:
#리프 노드일 때
if not cur.left and not cur.right:
rem_node = cur
cur = None
#자식 노드가 하나(왼쪽 자식)
elif not cur.right:
rem_node = cur
cur = cur.left
#자식 노드가 하나(오른쪽 자식)
elif not cur.left:
rem_node = cur
cur = cur.right
#자식 노드가 2개일때
else:
replace = cur.left #대체 노드
while replace.right:
replace = replace.right
#삭제 노드와 대체 노드의 값을 교환, 대체 노드를 삭제하면서 삭제된 노드를 받아옴
cur.data, replace.data = replace.data, cur.data
cur.left, rem_node = self.__remove_recursion(cur.left, replace.data)
return cur, rem_node
- insert_node
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def insert_node(self, node):
#노드 생성 코드 없음 (노드 생성에 따른 부담을 덜 수 있다)
cur = self.root
if cur == None:
self.root = node
return
while True:
parent = cur
if node.data < cur.data:
cur = cur.left
if not cur:
parent.left = node
return
else:
cur = cur.right
if not cur:
parent.right = node
return
This post is licensed under CC BY 4.0 by the author.