[CS공부] 9. 트리

악어·2024년 4월 19일
0

CS / 알고리즘 공부

목록 보기
10/10
post-thumbnail

깃허브 정리본

용어

  • node(노드): 트리에서 원소에 해당하는 부분

  • edge(가지): 트리에서 연결 관계를 나타내는 부분

  • root(루트): 트리의 가장 위쪽에 있는 노드

  • leaf(리프) = terminal node(단말노드) = external node(외부노드): 가장 아래쪽에 위치한 노드

  • non-terminal node(비단말노드) = internal node(내부노드): 리프를 제외한 노드

  • child(자식): 두 노드가 가지로 연결되었을 때 아래쪽 노드

  • parent(부모): 두 노드가 가지로 연결되었을 때 위쪽 노드, 모든 노드의 부모는 한개 뿐임

  • sibling(형제): 부모가 같은 노드들

  • ancestor(조상): 한 노드에서 위쪽 가지를 따라가면 만나는 모든 노드들

  • descendant(자손): 한 노드에서 아래쪽 가지를 따라가면 만나는 모든 노드들

  • level(레벨): 루트에서 얼마나 멀리 떨어져 있는지를 나타낸 것, 루트의 레벨은 0

  • degree(차수): 노드가 갖는 자식의 수

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

  • height(높이): 리프 레벨의 최댓값

  • subtree(서브트리): 트리의 일부로, 한 노드를 루트로 다시 구성되는 트리

  • None tree(빈트리) = null tree(널트리): 노드와 가지가 전혀 없는 트리

  • ordered tree(순서트리): 형제 노드의 순서 관계가 있는 트리(ex 작은값이 왼쪽)

  • unordered tree(무순서트리): 형제 노드의 순서 관계가 없는 트리

  • Breadth-First-Search(너비우선검색=폭우선검색=가로검색=수평검색): 낮은 레벨의 노드부터 검색하고, 한 레벨 검색이 끝나면 다음 레벨로 내려가는 방법

  • Depth-First-Search(깊이우선검색=세로검색=수직검색): 리프에 도달할 때까지 아래쪽으로 내려가면서 검색하고, 리프에 도달하면 일단 부모 노드로 돌아가 다른 자식 노드 검색

  • preorder(전위순회): 노드방문 -> 왼쪽자식 -> 오른쪽자식 순으로 진행

  • inorder(중위순회): 왼쪽자식 -> 노드방문 -> 오른쪽자식 순으로 진행

  • postorder(후위순회): 왼쪽자식 -> 오른쪽자식 -> 노드방문 순으로 진행

  • binary tree(이진트리): 모든 노드의 차수가 2 이하인 트리

  • left subtree(왼쪽서브트리): 이진트리에서 왼쪽 자식을 루트로 하는 서브트리

  • right subtree(오른쪽서브트리): 이진트리에서 오른쪽 자식을 루트로 하는 서브트리

  • complete binary tree(완전이진트리): 루트부터 아래쪽으로, 왼쪽에서 오른쪽으로 노드가 모두 채워져 있는 이진트리, 단 리프에서는 모든 노드가 채워지지 않아도 됨
    => N개의 노드를 저장할 수 있는 완전이진트리의 높이는 logN

  • binary search tree(이진검색트리): 모든 노드가 아래 두 조건을 만족하는 트리
    => 1. 왼쪽 서브트리 노드의 키값은 자신의 노드 키값보다 작다
    => 2. 오른쪽 서브트리 노드의 키값은 자신의 노드 키값보다 크다
    => 구조가 단순함
    => 중위순회의 DFS를 통해 노드값을 오름차순으로 얻을 수 있음
    => 아주 빠르게 검색 가능
    => 노드를 삽입하기 쉬움



실습

이진검색트리

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:
        p = self.root
        while True:
            if p is None:
                return None
            if key == p.key:
                return p.value
            elif key < p.key:
                p = p.left
            else:
                p = p.right
    
    def add(self, key: Any, value: Any) -> bool:
        
        def add_node(node: Node, key: Any, value: Any) -> None:
            if key == node.key:
                return False
            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:
        p = self.root
        parent = None
        is_left_child = True

        while True:
            if p is None:
                return False
            
            if key == p.key:
                break
            else:
                parent = p
                is_left_child = key < p.key
                p = p.left if key < p.key else 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(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


tree = BinarySearchTree()

while True:
    option = int(input("(1)삽입 (2)삭제 (3)검색 (4)덤프 (5)키의범위 (6)종료\n"))
    if option == 1:
        key = int(input("삽입할 키(자연수)를 입력하세요: "))
        value = input("삽입할 값(문자)를 입력하세요: ")
        if not tree.add(key, value):
            print("이미 등록된 키입니다.")
    elif option == 2:
        if tree.remove(int(input("삭제할 키(자연수)를 입력하세요: "))):
            print("삭제 완료")
        else:
            print("없는 키입니다.")
    elif option == 3:
        result = tree.search(int(input("검색할 키(자연수)를 입력하세요: ")))
        if result:
            print(f"해당 키에 해당하는 값은 '{result}' 입니다.")
        else:
            print("없는 키입니다.")
    elif option == 4:
        tree.dump()
    elif option == 5:
        print(f"키의 최소값은 {tree.min_key()}, 최대값은 {tree.max_key()}입니다.")
    elif option == 6:
        break


# 실행결과 ==========================================
(1)삽입 (2)삭제 (3)검색 (4)덤프 (5)키의범위 (6)종료
1
삽입할 키(자연수)를 입력하세요: 92 
삽입할 값(문자)를 입력하세요: 영호
(1)삽입 (2)삭제 (3)검색 (4)덤프 (5)키의범위 (6)종료
1
삽입할 키(자연수)를 입력하세요: 49
삽입할 값(문자)를 입력하세요: 민철
(1)삽입 (2)삭제 (3)검색 (4)덤프 (5)키의범위 (6)종료
1
삽입할 키(자연수)를 입력하세요: 33
삽입할 값(문자)를 입력하세요: 순자
(1)삽입 (2)삭제 (3)검색 (4)덤프 (5)키의범위 (6)종료
1
삽입할 키(자연수)를 입력하세요: 112
삽입할 값(문자)를 입력하세요: 민식
(1)삽입 (2)삭제 (3)검색 (4)덤프 (5)키의범위 (6)종료
1
삽입할 키(자연수)를 입력하세요: 7
삽입할 값(문자)를 입력하세요: 민지
(1)삽입 (2)삭제 (3)검색 (4)덤프 (5)키의범위 (6)종료
4
7 민지
33 순자
49 민철
92 영호
112 민식
(1)삽입 (2)삭제 (3)검색 (4)덤프 (5)키의범위 (6)종료
5
키의 최소값은 7, 최대값은 112입니다.
(1)삽입 (2)삭제 (3)검색 (4)덤프 (5)키의범위 (6)종료
3
검색할 키(자연수)를 입력하세요: 33
해당 키에 해당하는 값은 '순자' 입니다.
(1)삽입 (2)삭제 (3)검색 (4)덤프 (5)키의범위 (6)종료
3
검색할 키(자연수)를 입력하세요: 17
없는 키입니다.
(1)삽입 (2)삭제 (3)검색 (4)덤프 (5)키의범위 (6)종료
2
삭제할 키(자연수)를 입력하세요: 112
삭제 완료
(1)삽입 (2)삭제 (3)검색 (4)덤프 (5)키의범위 (6)종료
4
7 민지
33 순자
49 민철
92 영호
(1)삽입 (2)삭제 (3)검색 (4)덤프 (5)키의범위 (6)종료
6


정리

책의 마지막 부분인 트리에 대한 정리가 끝났다.
알고리즘 문제를 풀다가 우선순위 힙을 구현하는 과정이
이부분과 연관있어보여 8장 연결리스트보다 먼저 정리한다.

내용상으로는 어렵지 않았지만,
이를 밑바닥부터 혼자 구현해보라고 하면 할 수 있을지 잘 모르겠다.

손에 익혀 실제 구현문제가 나왔을때 써먹을 수 있게 해야겠다.



profile
냅다 회사부터 세워버린 개발자

0개의 댓글