본문 바로가기
4학기/자료구조

[자료구조] 트리

by sshnnne 2022. 6. 30.

 

- 트리 기본 용어

 

1. 루트

: 트리의 가장 위쪽 노드. 트리 하나에 한 개만 존재

 

2. 리프

: 가장 아래쪽 노드. (= 단말 노드 or 외부 노드)

 

3. 비단말 노드

: 리프를 제외한 노드. (= 내부 노드)

 

4. 자식

: 어떤 노드와 가지가 연결되었을 때 아래쪽 노드. 노드는 자식을 몇 개라도 가질 수 O

 

5. 부모

: 어떤 노드와 가지가 연결되었을 때 가장 위쪽 노드. 노드의 부모는 하나뿐. 루트는 부모를 가지지 X

 

6. 형제

: 부모가 같은 노드

 

7. 조상

: 어떤 노드에서 위쪽으로 가지를 따라가면 만나는 모든 노드

 

8. 자손

: 어떤 노드에서 아래쪽으로 가지를 따라가면 만나는 모든 노드

 

9. 레벨

: 루트에서 얼마나 떨어져있는지를 나타내는 척도. 가장 위쪾 노드 레벨 0, 가지가 아래로 뻗어 내려갈 때마다 레벨 += 1

 

10. 차수

: 각 노드가 갖는 자식의 수. 

( n진 트리 : 모든 노드의 차수가 n 이하인 트리)

 

11. 높이

: 루트에서 리프까지의 거리. 리프 레벨의 최댓값

 

12. 서브트리

: 어떤 노드를 루트로 하고 그 자손으로 구성된 트리

 

13. 빈 트리

: 노드와 가지 X

 

 

- 순서 트리와 무순서 트리 (ordered tree, unordered tree)

 

형제 노드의 순서 관계가 있으면 순서트리, 없으면 무순서 트리

 

- 순서 트리의 검색

 

1. 너비 우선 검색 (breadth-first search) ( = 폭 우선 검색, 가로 검색, 수평 검색)

낮은 레벨부터 왼 -> 오 검색 후 다음 레벨로 내려감

 

2. 깊이 우선 검색 (depth-first search) ( = 세로 검색, 수직 검색)

1) 전위 순회 (preorder)

노드 방문 -> 왼쪽 자식 -> 오른쪽 자식

 

2) 중위 순회 (inorder)

왼쪽 자식 -> 노드 방문 -> 오른쪽 자식

 

3) 후위 순회 (postorder)

왼쪽 자식 -> 오른쪽 자식 -> 노드 방문

 

 

- 이진 트리 (binary tree)

노드가 왼쪽 자식과 오른쪽 자식만을 갖는 트리. 두 자식 가운데 하나 or 둘 다 존재하지 않는 노드 있어도 상관X

왼쪽 자식과 오른쪽 자식을 구분함.

 

- 완전 이진 트리 (complete binary tree)

:루트부터 아래쪽 레벨로 노드가 가득 차 있고, 같은 레벨 안에서 왼 -> 오로 노드가 채워져 있는 이진 트리

1) 마지막 레벨 제외 모든 레벨에 노드가 가득 참.

2) 마지막 레벨에 한해서 왼 -> 오로 노드를 채우되 반드시 끝까지 채우지 않아도 됨

3) 높이 k인 완이트가 가질 수 있는 노드의 수 최대 

 

2\combi{^{k+1}}-1

4) n개의 노드 저장할 수 있는 완이트의 높이 : log n

 

- 균형 검색 트리 (self-balancing search)

: 높이를 O(logn) 으로 제한하여 고안된 검색 트리

1) 이진 균형 검색 트리 : AVL tree, red-black tree

2) 이진이 아닌 균형 검색 트리 : B tree, 2-3 tree

 

- 이진 검색 트리 (binary search tree)

1) 왼쪽 서브트리 노드 키값 < 자신의 노드 키값

2) 오른쪽 서브트리 노드 키값 > 자신의 노드 키값

-> 키값 동일한 노드 복수 존재 X

 

 

- 이진 검색 트리 함수

 

from __future__ import annotations
from typing import Any, Type

class Node:
    # 이진 검색 트리의 노드

    def __init__(self, key:Any, value: Any, left: Node = None, right: Node = None):

        self.key = key #키
        self.value = value #값
        self.left = left #왼쪽 포인터 
        self.right = right #오른쪽 포인터


class BinarySearchTree:
    # 이진 검색 트리

    def __init__(self):
        self.root = None #루트

    def search(self, key: Any) -> Any:
        # 키가 key인 노드를 검색

        p = self.root # 루트에 주목

        while True:

            
            if p is None: return None #더 이상 진행할 수 X -> 검색 실패
            #근데 왜 이렇게 if 두 개로 쪼갠거지? elif 쓰면 안 되나? 
            if key == p.key: return p.value #검색 성공

            elif key < p.key: p = p.left #key 쪽이 작으면 왼쪽 서브트리에서 검색

            else: p = p.right #key 쪽이 크면 오른쪽 서브트리에서 검색


    def add(self, key: Any, value: Any) -> bool:
        #키가 key이고 값이 value인 노드를 삽입

        def add_node(node: Node, key: Any, value: Any) -> None:
            #node를 루트로 하는 서브트리에 키가 key이고 값이 value인 노드를 삽입

            if key == node.key:
                return False #key가 이미 존재

            elif key < node.key: 
                if node.left is None:
                    node.left = Node(key, value, None, None)

                else:
                    add_node(node.left, key, value)

            else:
                if node.right is None:
                    node.right = Node(key, value, None, None)

                else:
                    add_node(node.right, key, value)
            return True


            if self.root is None:
                self.root = Node(key, value, None, None)
                return True

            else:
                return add_node(self.root, key, value)


        def remove(self, key: Any) -> bool:
            #키가 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:

                    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:

                    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:
                        parent = left
                        left = left.right
                        is_left_child = False

                    p.key = left.key
                    p.value = left.value

                    if is_left_child:
                        parent.left = left.left

                    else:
                        parent.right = left.left

                    return True


        def dump(self) -> None:
            #모든 노드를 키의 오름차순으로 출력

            def print_subtree(node: Node):

                if node is not None:
                    print_subtree(node.left)
                    print(f'{node.key} {node.value}')
                    print_subtree(node.right)

                print_subtree(self.root)


        def min_key(self) -> Any:

            if self.root is None:
                return None

            p = self.root

            while p.left is not None:
                p = p.left
                return p.key

        def max_key(self) -> Any:

            if self.root is None:
                return None

            p = self.root

            while p.right is not None:
                p = p.right

            return p.key

    def dump(self, reverese = False) -> None:

        def print_subtree(node: Node):
            #node를 루트로 하는 서브트리의 노드를 키의 오름차순으로 출력
            
            if node is not None:
                print_subtre(node.left)
                print(f'{node.key} {node.value}')
                print_subtree(node.right)

        def print_subtree_rev(node: Node):
            #node를 루트로 하는 서브트리의 노드를 키의 내림차순으로 출력

            if node is not None:
                print_subtree_rev(node.right)
                print(f'{node.key} {node.value}')
                print_subtree_rev(node.left)

        print_subtree_rev(self.root) if reverse else print_subtree(self.root)

 

 

1. search() 함수

 

def search(self, key: Any) -> Any:
        # 키가 key인 노드를 검색

        p = self.root # 루트에 주목

        while True:

            
            if p is None: return None #더 이상 진행할 수 X -> 검색 실패
  
            if key == p.key: return p.value #검색 성공

            elif key < p.key: p = p.left #key 쪽이 작으면 왼쪽 서브트리에서 검색

            else: p = p.right #key 쪽이 크면 오른쪽 서브트리에서 검색

 

1) 주목하는 루트 노드 p로 설정

2) p가 None이면 검색 실패 후 종료

3) key = p : 검색 성공 후 종료

key < p : 주목 노드를 왼쪽 자식 노드로 옮김

key > p : 주목 노드를 오른쪽 자식 노드로 옮김

 

 

 

2. add() 함수

(노드 삽입 시 주의할 점 : 노드 삽입 후 트리의 형태가 이진 검색 트리의 조건을 유지해야 함)

삽입할 위치 찾아낸 후에 수행해야 함

 

def add(self, key: Any, value: Any) -> bool:
        #키가 key이고 값이 value인 노드를 삽입

        def add_node(node: Node, key: Any, value: Any) -> None:
            #node를 루트로 하는 서브트리에 키가 key이고 값이 value인 노드를 삽입

            if key == node.key:
                return False #key가 이미 존재

            elif key < node.key: 
                if node.left is None:
                    node.left = Node(key, value, None, None)

                else:
                    add_node(node.left, key, value)

            else:
                if node.right is None:
                    node.right = Node(key, value, None, None)

                else:
                    add_node(node.right, key, value)
            return True


            if self.root is None:
                self.root = Node(key, value, None, None)
                return True

            else:
                return add_node(self.root, key, value)

 

 

1) 주목하는 루트 node로 설정

2) key = node : 삽입 실패 후 종료

key < node:

- 왼쪽 자식 노드 X : 그 자리에 노드 삽입 후 종료

- 왼쪽 자식 노드 O : 주목 노드 = 왼쪽 자식 노드

key > node:

- 오른쪽 자식 노드 X : 그 자리에 노드 삽입 후 종료

- 오른쪽 자식 노드 O : 주목 노드 = 오른쪽 자식 노드

 

 if self.root is None:
                self.root = Node(key, value, None, None)
                return True

            else:
                return add_node(self.root, key, value)

 

- root 가 None일 때 :

트리가 비어있으므로 루트만으로 구성된 트리를 만들어야 함

왼/오 포인터 모두 None인 노드 생성 후 그 노드를 루트가 참조하도록 한다

 

- root != None:

내부 함수 add_node()를 호출해 노드 삽입

add_node() 는 node를 루트로 하는 서브 트리에 key value 삽입 (recursive)

 

 

3. remove() 함수

 

def remove(self, key: Any) -> bool:
            #키가 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:

                    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:

                    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:
                        parent = left
                        left = left.right
                        is_left_child = False

                    p.key = left.key
                    p.value = left.value

                    if is_left_child:
                        parent.left = left.left

                    else:
                        parent.right = left.left

                    return True

 

- 자식 노드가 없는 노드를 삭제하는 경우

포인터 = None으로 설정하면 가리키는 노드가 없어 이검트에서 삭제됨

 

- 자식 노드가 1개인 노드를 삭제하는 경우

1) 삭제할 노드가 부모 노드의 왼쪽 자식인 경우 :

부모의 왼쪽 포인터가 삭제할 노드의 자식을 가리키도록 업데이트

2) 삭제할 노드가 부모 노드의 오른쪽 자식인 경우 :

부모의 오른쪽 포인터가 삭제할 노드의 자식을 가리키도록 업데이트

 

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:

                    if p is self.root:
                        self.root = p.left

                    elif is_left_child:
                        parent.left = p,left

                    else:
                        parent.right = p.left

 

- 자식 노드가 2개인 노드를 삭제하는 경우

1) 삭제할 노드의 왼쪽 서브트리에서 키값 가장 큰 노드 검색

2) 검색한 노드를 삭제 위치로 이동

3) 옮긴 노드 삭제

옮긴 노드에 자식 X : 자식 노드가 없는 노드의 삭제에 따라 노드 삭제

옮긴 노드에 자식이 1개 : 자식 노드가 1개인 노드의 삭제에 따라 노드 삭제

 

 

else:
                    parent = p
                    left = p.left
                    is_left_child = True

                    while left.right is not None:
                        parent = left
                        left = left.right
                        is_left_child = False

                    p.key = left.key
                    p.value = left.value

                    if is_left_child:
                        parent.left = left.left

                    else:
                        parent.right = left.left

                    return True

 

 

4. dump() 함수

스캔은 중위 순회의 깊이 우선 검색

 

def dump(self) -> None:
            #모든 노드를 키의 오름차순으로 출력

            def print_subtree(node: Node):

                if node is not None:
                    print_subtree(node.left)
                    print(f'{node.key} {node.value}')
                    print_subtree(node.right)

                print_subtree(self.root)

 

print_subtree() : node를 루트로 하는 서브트리의 노드를 오름차순으로 출력하는 재귀 함수 

1) 노드 1을 참조하는 왼쪽 포인터를 전달하여 print_subtree() 재귀 호출

2) 자신의 노드 데이터인 2 출력

3) 노드 4를 참조하는 오른쪽 포인터를 전달하여 print_subtree() 재귀 호출

 

 

 

5. min_key(), max_key() 함수

맨 끝인 None을 만날 때까지 왼 or 오 자식을 따라가는 알고리즘

 

def min_key(self) -> Any:

            if self.root is None:
                return None

            p = self.root

            while p.left is not None:
                p = p.left
                return p.key

        def max_key(self) -> Any:

            if self.root is None:
                return None

            p = self.root

            while p.right is not None:
                p = p.right

            return p.key

 

 

 

 

 

'4학기 > 자료구조' 카테고리의 다른 글

후위 수식(postfix) 정리  (0) 2022.09.30
[자료구조] 수식 표현과 평가  (0) 2022.06.30