이진 탐색 트리 (Binary Search Tree)

이진 탐색 μ•Œκ³ λ¦¬μ¦˜

  • μ •λ ¬λœ λ°μ΄ν„°λ‘œ 된 리슀트(λ°°μ—΄μ΄λ‚˜ μ—°κ²° 리슀트)κ°€ 인수둜 듀어왔을 λ•Œ, μš”μ†Œ 쀑에 찾고자 ν•˜λŠ” 데이터가 μžˆλŠ”μ§€ μ•Œμ•„λ³΄λŠ” μ•Œκ³ λ¦¬μ¦˜ binary search algorithm

λ”•μ…”λ„ˆλ¦¬μ˜ λ‚΄λΆ€ κ΅¬ν˜„

  • λ”•μ…”λ„ˆλ¦¬λŠ” <key, item>의 쌍으둜 된 집합
  • λ‚΄λΆ€μ μœΌλ‘œ BST, ν•΄μ‹œ ν…Œμ΄λΈ” 두 가지 자료 ꡬ쑰둜 κ΅¬ν˜„ κ°€λŠ₯
  • C++의 map은 BST의 λ³€ν˜•μΈ κ· ν˜• 이진 트리, κ·Έ μ€‘μ—μ„œλ„ λ ˆλ“œ λΈ”λž™ 트리λ₯Ό μ΄μš©ν•΄μ„œ κ΅¬ν˜„
  • μžλ°”μ—μ„œλŠ” HashMapκ³Ό TreeMap을 λͺ¨λ‘ μœ μ € ν”„λ‘œκ·Έλž˜λ¨Έκ°€ λ‚΄λΆ€ κ΅¬ν˜„μ„ 선택할 수 μžˆλ„λ‘ 함
  • μžλ°”λŠ” λ ˆλ“œ λΈ”λž™ 트리λ₯Ό μ‚¬μš©ν•˜μ—¬ κ΅¬ν˜„ν•¨

이진 탐색 트리

  • 검색 νš¨μœ¨μ„ 높이기 μœ„ν•΄ λ§Œλ“  이진 트리
  • λ…Έλ“œμ— 데이터λ₯Ό 직접 μ €μž₯ν•˜μ§€ μ•ŠλŠ”λ‹€
  • 데이터에 λŒ€ν•œ 참쑰만 μ €μž₯
  • λ°μ΄ν„°μ˜ μ°Έμ‘°λ₯Ό μ €μž₯ν•˜κ³  μžˆλŠ” λ…Έλ“œλ₯Ό λ‚˜νƒ€λ‚΄λŠ” ν‚€κ°€ μ€‘μš”
  • ν‚€λ§Œ λΉ λ₯΄κ²Œ 검색 ν•  수 μžˆλ‹€λ©΄ λ°μ΄ν„°λŠ” μ°Έμ‘°λ₯Ό μ΄μš©ν•΄μ„œ λ°”λ‘œ μ ‘κ·Ό ν•  수 μžˆλ‹€
  • 이진 탐색 트리의 μ •μ˜
    1. λͺ¨λ“  ν‚€λŠ” μœ μΌν•©λ‹ˆλ‹€.
    2. μ–΄λ–€ λ…Έλ“œλ₯Ό νŠΉμ •ν–ˆμ„ λ•Œ 이 λ…Έλ“œμ˜ ν‚€ 값은 μ™Όμͺ½ μ„œλΈŒ 트리의 κ·Έ μ–΄λ–€ 킀보닀 ν½λ‹ˆλ‹€.
    3. μ–΄λ–€ λ…Έλ“œλ₯Ό νŠΉμ •ν–ˆμ„ λ•Œ 이 λ…Έλ“œμ˜ ν‚€ 값은 였λ₯Έμͺ½ μ„œλΈŒ 트리의 κ·Έ μ–΄λ–€ ν‚€ 값보닀 μž‘μŠ΅λ‹ˆλ‹€.
    4. (μž¬κ·€μ  μ •μ˜) λ…Έλ“œμ˜ μ„œλΈŒ νŠΈλ¦¬λ„ 이진 탐색 νŠΈλ¦¬μž…λ‹ˆλ‹€.

    binary search tree

이진 탐색 트리의 κ΅¬ν˜„

class TreeNode:
    def __init__(self, key):
        self.__key=key
        self.__left=None
        self.__right=None
        self.__parent=None

    def __del__(self):
        print('key {} is deleted'.format(self.__key))

    @property
    def key(self):
        return self.__key

    @key.setter
    def key(self, key):
        self.__key=key

    @property
    def left(self):
        return self.__left

    @left.setter
    def left(self, left):
        self.__left=left

    @property
    def right(self):
        return self.__right

    @right.setter
    def right(self, right):
        self.__right=right

    @property
    def parent(self):
        return self.__parent

    @parent.setter
    def parent(self, p):
        self.__parent=p


class BST:
    def __init__(self):
        self.root=None

    def get_root(self):
        return self.root

    def preorder_traverse(self, cur, func):
        if not cur:
            return

        func(cur)
        self.preorder_traverse(cur.left, func)
        self.preorder_traverse(cur.right, func)

    # keyκ°€ μ •λ ¬λœ μƒνƒœλ‘œ 좜λ ₯
    def inorder_traverse(self, cur, func):
        if not cur:
            return

        self.inorder_traverse(cur.left, func)
        func(cur)
        self.inorder_traverse(cur.right, func)

    # 편의 ν•¨μˆ˜
    # cur의 μ™Όμͺ½ μžμ‹μ„ left둜 λ§Œλ“ λ‹€.
    def __make_left(self, cur, left):
        cur.left=left
        if left:
            left.parent=cur

    # 편의 ν•¨μˆ˜
    # cur의 였λ₯Έμͺ½ μžμ‹μ„ right둜 λ§Œλ“ λ‹€.
    def __make_right(self, cur, right):
        cur.right=right
        if right:
            right.parent=cur

    def insert(self, key):
        new_node=TreeNode(key)

        cur=self.root
        if not cur:
            self.root=new_node
            return

        while True:
            parent=cur
            if key < cur.key:
                cur=cur.left
                if not cur:
                    self.__make_left(parent, new_node)
                    return
            else:
                cur=cur.right
                if not cur:
                    self.__make_right(parent, new_node)
                    return 

    def search(self, target):
        cur=self.root
        while cur:
            if cur.key==target:
                return cur
            elif cur.key > target:
                cur=cur.left
            elif cur.key < target:
                cur=cur.right
        return cur

    def __delete_recursion(self, cur, target):
        if not cur:
            return None
        elif target < cur.key:
            new_left=self.__delete_recursion(cur.left, target)
            self.__make_left(cur, new_left)
        elif target > cur.key:
            new_right=self.__delete_recursion(cur.right, target)
            self.__make_right(cur, new_right)
        else:
            if not cur.left and not cur.right:
                cur=None
            elif not cur.right:
                cur=cur.left
            elif not cur.left:
                cur=cur.right
            else:
                replace=cur.left
                replace=self.max(replace)
                cur.key, replace.key=replace.key, cur.key
                new_left=self.__delete_recursion(cur.left, replace.key)
                self.__make_left(cur, new_left)
        return cur

    def delete(self, target):
        new_root=self.__delete_recursion(self.root, target)
        self.root=new_root

    def min(self, cur):
        while cur.left != None:
            cur=cur.left
        return cur 

    def max(self, cur):
        while cur.right != None:
            cur=cur.right
        return cur

    def prev(self, cur):
        # μ™Όμͺ½ μžμ‹μ΄ μžˆλ‹€λ©΄
        # μ™Όμͺ½ μžμ‹μ—μ„œ κ°€μž₯ 큰 λ…Έλ“œ
        if cur.left:
            return self.max(cur.left)
        
        # λΆ€λͺ¨ λ…Έλ“œλ₯Ό λ°›μ•„μ˜¨λ‹€
        parent = cur.parent
        # ν˜„μž¬ λ…Έλ“œκ°€ λΆ€λͺ¨ λ…Έλ“œμ˜ μ™Όμͺ½ μžμ‹μ΄λ©΄ 
        while parent and cur==parent.left:
            cur=parent
            parent=parent.parent
        
        return parent

    def next(self, cur):
        # 였λ₯Έμͺ½ μžμ‹μ΄ μžˆλ‹€λ©΄
        # 였λ₯Έμͺ½ μžμ‹μ—μ„œ κ°€μž₯ μž‘μ€ λ…Έλ“œ
        if cur.right:
            return self.min(cur.right)
        
        # λΆ€λͺ¨ λ…Έλ“œλ₯Ό λ°›μ•„μ˜¨λ‹€
        parent = cur.parent
        # ν˜„μž¬ λ…Έλ“œκ°€ λΆ€λͺ¨ λ…Έλ“œμ˜ 였λ₯Έμͺ½ μžμ‹μ΄λ©΄
        # λ£¨νŠΈμ— λ„λ‹¬ν•˜κ±°λ‚˜
        # ν˜„μž¬ λ…Έλ“œκ°€ λΆ€λͺ¨ λ…Έλ“œμ˜ μ™Όμͺ½ μžμ‹μ΄ 될 λ•ŒκΉŒμ§€
        # 계속 λΆ€λͺ¨ λ…Έλ“œλ‘œ 이동
        while parent and cur==parent.right:
            cur=parent
            parent=parent.parent
        
        return parent      

if __name__=="__main__":
    print('*'*100)
    bst=BST()

    bst.insert(6)
    bst.insert(3)
    bst.insert(2)
    bst.insert(4)
    bst.insert(5)
    bst.insert(8)
    bst.insert(10)
    bst.insert(9)
    bst.insert(11)

    f=lambda x: print(x.key, end='  ')

    #bst.preorder_traverse(bst.get_root(), f)
    bst.inorder_traverse(bst.get_root(), f)
    print()
    
    searched_node = bst.search(8)
    if searched_node:
        print(f'searched key : {searched_node.key}')
        
        prev_node = bst.prev(searched_node)
        if prev_node:
            print(f'prev key : {prev_node.key}')
        else:
            print(f'this is the first key of the BST')

        next_node = bst.next(searched_node)
        if next_node:
            print(f'next key : {next_node.key}')
        else:
            print(f'this is the last key of the BST')
    else:
        print('there is no such key')
    print()


    print (f'MIN(bst) : {bst.min(bst.get_root()).key}')
    print(f'MAX(bst) : {bst.max(bst.get_root()).key}')

    #bst.delete(9)
    #bst.delete(8)
    bst.delete(6)

    #print(bst.delete(15))

    bst.preorder_traverse(bst.get_root(), f)
    # bst.inorder_traverse(bst.get_root(), f)
    print()
    print('*'*100)

이진 탐색 트리의 단점

  • μ •λ ¬λœ 데이터가 μ‚½μž…λ˜λ©΄ 각 λ ˆλ²¨λ§ˆλ‹€ λ…Έλ“œκ°€ ν•˜λ‚˜μ”©λ§Œ μžˆλŠ” νŽΈν˜• 이진 νŠΈλ¦¬κ°€ 됨
  • μ—°κ²° λ¦¬μŠ€νŠΈμ™€ κ°™μŒ
  • μ‚½μž…, μ‚­μ œ, 탐색 λͺ¨λ‘ O(n)

=> 이λ₯Ό λ³΄μ™„ν•œ 이진 탐색 νŠΈλ¦¬κ°€ λ ˆλ“œ λΈ”λž™ νŠΈλ¦¬κ°€ ν¬ν•¨λ˜λŠ” κ· ν˜• 이진 트리