[Data Structure] 이진 탐색 트리

개념

이진 탐색 트리(Binary Search Tree)는 원소의 크기에 따라 노드의 위치를 정의한 것이고, 이진 트리 기반의 탐색을 위한 자료구조이다.

탐색할 때, 부모 노드의 값을 기준으로 작으면 왼쪽, 크면 오른쪽에 넣기 때문에 시간복잡도는 O(n)이다. 따라서, 이진 트리 구조에서 탐색할 때 효과적인 방법이다.

조건

  • 모든 원소는 서로 다른 유일 키를 갖는다.
  • 왼쪽 서브 트리에 있는 원소들의 키는 루트의 키보다 작다.
  • 오른쪽 서브 트리에 있는 원소들의 키는 루트의 키보다 크다.
  • 왼쪽 서브 트리와 오른쪽 서브 트리도 이진 탐색 트리이다.

트리의 노드

class Node:
    def __init__(self,data):
        self.data = data # 현재 노드의 값 
        self.left = None # 좌측 노드
        self.right = None # 우측 노드

삽입

트리의 특정 위치에 원소를 넣는 과정이다. 먼저 탐색을 수행하여 이진트리에 같은 원소가 있는지 확인하고 있다면 삽입하지 않는다. 만약 없다면, 탐색 실패가 발생한 위치에 원소를 삽입한다.

코드
# 삽입
def insert(self, data):
    # 트리의 루트 노드가 존재하는지 아닌지 판단
    if self.root is None:
        self.setRoot(data)  # 루트 노드가 없는 경우(제일 처음에 들어온 데이터가 루트 노드가 됨)
    else:
        self._insert_value(self.root, data)

def _insert_value(self, cur, data):
    # 부모 노드의 키보다 작으면 좌측, 크면 우측으로 보냄
    if data < cur.data:
        if cur.left:  # 이미 요소가 있다면 삽입 메서드 한번 더
            self._insert_value(cur.left, data)
        else:
            cur.left = Node(data)
    else:
        if cur.right:
            self._insert_value(cur.right, data)
        else:
            cur.right = Node(data)

탐색

  • 루트 노드부터 기준으로 잡고 탐색 시작
  • 비교
    • 키 값기준 노드의 키 값 같을 경우
    • 키 값기준 노드의 키 값 보다 작을 경우
      • 좌측 서브트리 탐색
    • 키 값기준 노드의 키 값 보다 클 경우
      • 우측 서브트리 탐색
# 탐색
def find(self,data):
    if self._find_node(self.root,data):
        return True
    else:
        return False


def _find_node(self,cur,data):
    if cur is None:
        return False
    elif data == cur.data:
        return True
    elif data < cur.data:
        return self._find_node(cur.left,data)
    else:
        return self._find_node(cur.right,data)

삭제

삭제는 삽입과 삭제보다 고려해야 될 점이 많아 복잡하다. 왜냐하면 삭제 후에도 이진 탐색을 계속 유지해야하기 때문이다.

삭제할 노드를 기준으로

  • 자식 노드가 없을 경우
    해당 노드(삭제할 노드)를 삭제하고, 부모 노드에서도 정보(좌측,우측)를 지운다.

    def remove(self)
  • 자식 노드가 하나인 경우

    해당 노드를 삭제하고, 자식 노드를 그 자리에 대신 넣으면 된다. 즉, 자식 노드를 할아버지 노드에 연결한다.

  • 자식 노드가 둘인 경우

    해당 노드를 삭제하고, 그 자리에 대신할 노드를 넣어야한다. 이 경우에는 직계 자식 노드뿐만 아니라 전체 자손 노드 중에서 후계자를 찾아야한다. 자리를 물려줘도 이진 탐색 트리가 유지되야 하는데, 자식 노드를 넣으면 이진 탐색 트리 구조가 깨질 수도 있기 때문이다.

    후계자의 조건

    왼쪽 서브트리에 있는 노드의 키 값 보다는 커야하고 오른쪽 서브트리에 있는 노드의 키 값 보다는 작아야한다.

    • 왼쪽 서브트리에서 가장 큰 값
    • 오른쪽 서브트리에서 가장 작은 값
# 삭제
def delete(self,data):
    self._delete_node(self.root,data) # 루트 노드부터 시작하므로 재귀를 시작해야하므로
def _delete_node(self,cur,data):
    if cur is None:
        return None
    elif data < cur.data:
        cur.left = self._delete_node(cur.left,data) # 왼쪽 자식 노드 정보 제거
        return cur
    elif data > cur.data:
        cur.right = self._delete_node(cur.right,data) # 오른쪽 자식 노드 정보 제거
        return cur
    else: # data == cur.data
        # Node to be removed has no children.
        if cur.left is None and cur.right is None:
            return None
        # Node to be removed has own children.
        elif cur.left is not None and cur.right is None:
            return cur.left
        elif cur.left is None and cur.right is not None:
            return cur.right
        # Node to be removed has two children.
        else:
            # 오른쪽 서브트리에서 가장 작은 값
            min_node = self._successor_node(cur.right)
            cur.data = min_node.data # cur.left 정보는 유지하기 위해 min_node.data만 가져옴
            self._delete_node(cur.right,min_node.data) # cur.right에는 이미 저장하고 있으므로 저장 안해도
            return cur
# 후계 노드 찾기
def _successor_node(self,cur):
    while cur.left is not None:
        cur = cur.left
    return cur

전체 코드

class Node:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None


class BinarySearchTree(object):
    def __init__(self):
        self.root = None  # 트리의 루트 노드
        self.pre_order_lst = []  # 전위 순회 리스트
        self.post_order_lst = []  # 후위 순회 리스트

    # 루트 노드 설정
    def setRoot(self, data):
        self.root = Node(data)

    # 삽입
    def insert(self, data):
        # 트리의 루트 노드가 존재하는지 아닌지 판단
        if self.root is None:
            self.setRoot(data)  # 루트 노드가 없는 경우(제일 처음에 들어온 데이터가 루트 노드가 됨)
        else:
            self._insert_value(self.root, data)

    def _insert_value(self, cur, data):
        # 부모 노드의 키보다 작으면 좌측, 크면 우측으로 보냄
        if data < cur.data:
            if cur.left:  # 이미 요소가 있다면 삽입 메서드 한번 더
                self._insert_value(cur.left, data)
            else:
                cur.left = Node(data)
        else:
            if cur.right:
                self._insert_value(cur.right, data)
            else:
                cur.right = Node(data)


    # 탐색
    def find(self,data):
        if self._find_node(self.root,data):
            return True
        else:
            return False


    def _find_node(self,cur,data):
        if cur is None:
            return False
        elif data == cur.data:
            return True
        elif data < cur.data:
            return self._find_node(cur.left,data)
        else:
            return self._find_node(cur.right,data)

    # 삭제
    def delete(self,data):
        self._delete_node(self.root,data) # 루트 노드부터 시작하므로 재귀를 시작해야하므로

    def _delete_node(self,cur,data):
        if cur is None:
            return None
        elif data < cur.data:
            cur.left = self._delete_node(cur.left,data) # 왼쪽 자식 노드 정보 제거
            return cur
        elif data > cur.data:
            cur.right = self._delete_node(cur.right,data) # 오른쪽 자식 노드 정보 제거
            return cur
        else: # data == cur.data
            # Node to be removed has no children.
            if cur.left is None and cur.right is None:
                return None
            # Node to be removed has own children.
            elif cur.left is not None and cur.right is None:
                return cur.left
            elif cur.left is None and cur.right is not None:
                return cur.right
            # Node to be removed has two children.
            else:
                # 오른쪽 서브트리에서 가장 작은 값
                min_node = self._successor_node(cur.right)
                cur.data = min_node.data # cur.left 정보는 유지하기 위해 min_node.data만 가져옴
                self._delete_node(cur.right,min_node.data) # cur.right에는 이미 저장하고 있으므로 저장 안해도
                return cur

    # 후계 노드 찾기
    def _successor_node(self,cur):
        while cur.left is not None:
            cur = cur.left
        return cur


    # 전위 순회
    def _pre_order_traverse(self):
        if self.root is not None:  # 루트 노드가 존재한다면 탐색
            self._pre_order(self.root)

    def _pre_order(self, cur):
        self.pre_order_lst.append(cur.data)
        if cur.left is not None:
            self._pre_order(cur.left)
        if cur.right is not None:
            self._pre_order(cur.right)

    # 후위 순회
    def _post_order_traverse(self):
        if self.root is not None:
            self._post_order(self.root)

    def _post_order(self, cur):
        if cur.left is not None:
            self._post_order(cur.left)
        if cur.right is not None:
            self._post_order(cur.right)
        self.post_order_lst.append(cur.data)


arr = [11,10,13,12,15,14,16,17]
bst = BinarySearchTree()
for val in arr:
    bst.insert(val)

참고

좋은 웹페이지 즐겨찾기