본문 바로가기
Algorithm/data structure

[자료 구조] 트리(Tree) - 2. 파이썬 구현

by 히욤 2021. 6. 25.

* 해당 글은 패스트캠퍼스의 자료구조 알고리즘 강의를 듣고 정리하였습니다. 저작권은 패스트캠퍼스에 있습니다.

📌 노드 클래스 만들기

✔️ Node 는 data값과 brach에 대한 정보를 가지고 있다.

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

 

 

📌 이진 탐색 트리 삽입/ 탐색 구현

✔️ 이진 탐색 트리를 control 하는 class를 생성한다. => NodeMgmt

class NodeMgmt:
    def __init__(self, head):
    	# 제일 위에있는 root node를 설정
        self.head = head
    
    def insert(self, value):
    	# root node 부터 시작
        self.current_node = self.head
        while True:
        	# 삽입할 데이터가 현재 노드보다 작다면 왼쪽으로
            if value < self.current_node.value:
            	# 값이 존재하면 한번 더 내려가야함
                if self.current_node.left != None:
                    self.current_node = self.current_node.left
                # 값이 없으면 삽입후 종료
                else:
                    self.current_node.left = Node(value)
                    break
            # 삽입할 데이터가 현재 노드보다 크다면 오른쪽으로
            else:
                if self.current_node.right != None:
                    self.current_node = self.current_node.right
                else:
                    self.current_node.right = Node(value)
                    break
                    
	def search(self, value):
    	# root node 부터 시작
        self.current_node = self.head
        while self.current_node:
        	# 찾는 값이랑 동일하면 종료
            if self.current_node.value == value:
                return True
            # 찾는 값이 현재 노드보다 작다면 왼쪽으로
            elif value < self.current_node.value:
                self.current_node = self.current_node.left
            # 찾는 값이 현재 노드보다 크다면 오른쪽으로
            else:
                self.current_node = self.current_node.right
        # 동일한 값을 찾지 못하면 실패
        return False     

 

✔️ 구현 결과를 확인

head = Node(1)
BST = NodeMgmt(head)
BST.insert(2)
BST.insert(3)
BST.insert(0)
BST.insert(4)
BST.insert(8)
print(BST.search(0)) # True
print(BST.search(8)) # True
print(BST.search(-1)) # False

 

 

 

📌 이진 탐색 트리 삭제 구현

이진 탐색 트리에서 삭제는 구현하기 매우 복잡하다. 경우를 크게 3개로 다음과 같이 나눈다.

 

  1. Leaf Node 삭제
  2. Child Node 가 하나인 Node 삭제
  3. Child Node 가 두개인 Node 삭제

 

✔️ case1: Leaf Node 삭제

  • Leaf Node는 자식 노드가 없는 노드
  • 이 경우 노드만 제거하면 끝남! 제일 간단한다.
  • 삭제할 노드의 (부모 노드)가 삭제할 노드를 가리키지 않도록 한다.
    • parent_node.right = None 또는 parent_node.left = None

Delete Leaf node

 

✔️ case2: Child Node 가 하나인 Node 삭제

  • 삭제할 노드의 (부모 노드)가 삭제할 노드의 (자식 노드)를 가리키게 함.
    • parent_node.right/left = current_node.right/left

 

✔️  case3: Child Node 가 두개인 Node 삭제

  • 이 경우가 제일 복잡하다.
  • 삭제할 노드의 (오른쪽 자식 중, 가장 작은 노드)를 (삭제할 노드의 부모 노드)가 가리키도록 한다.
  • 이 방법도 크게 2가지 case로 나눠진다
    1. [삭제할 노드의 오른쪽 자식중 가장 작은 노드]가 [오른쪽 자식]이 없는 경우
      • [삭제할 노드의 오른쪽 자식중 가장 작은 노드]를 [삭제할 노드 위치]로 이동시킴
    2. [삭제할 노드의 오른쪽 자식중 가장 작은 노드]가 [오른쪽 자식]이 있는 경우
      • [삭제할 노드의 오른쪽 자식중 가장 작은 노드]를 [삭제할 노드 위치]로 이동시킴
      • [삭제할 노드의 오른쪽 자식중 가장 작은 노드]의 오른쪽 자식을 [삭제할 노드의 오른쪽 자식중 가장 작은 노드 의 부모노드]의 [왼쪽 자식]으로 설정함

1 삭제할 노드의 오른쪽 자식중 가장 작은 노드가 오른쪽 자식이 없는 경우
2 삭제할 노드의 오른쪽 자식중 가장 작은 노드가 오른쪽 자식이 있는 경우

 

 

✔️ 삭제 구현 코드

class NodeMgmt:
    def __init__(self, head):
        self.head = head
    
    def delete(self, value):
        # 삭제할 노드 탐색
        searched = False
        self.current_node = self.head
        self.parent = self.head
        while self.current_node:
            if self.current_node.value == value:
                searched = True
                break
            elif value < self.current_node.value:
                self.parent = self.current_node
                self.current_node = self.current_node.left
            else:
                self.parent = self.current_node
                self.current_node = self.current_node.right

        if searched == False:
            return False    

        # case1
        if  self.current_node.left == None and self.current_node.right == None:
            if value < self.parent.value:
                self.parent.left = None
            else:
                self.parent.right = None
        
        # case2
        elif self.current_node.left != None and self.current_node.right == None:
            if value < self.parent.value:
                self.parent.left = self.current_node.left
            else:
                self.parent.right = self.current_node.left
        elif self.current_node.left == None and self.current_node.right != None:
            if value < self.parent.value:
                self.parent.left = self.current_node.right
            else:
                self.parent.right = self.current_node.right        
        
        # case 3
        elif self.current_node.left != None and self.current_node.right != None:
            # case3-1
            if value < self.parent.value:
                self.change_node = self.current_node.right
                self.change_node_parent = self.current_node.right
                while self.change_node.left != None:
                    self.change_node_parent = self.change_node
                    self.change_node = self.change_node.left
                if self.change_node.right != None:
                    self.change_node_parent.left = self.change_node.right
                else:
                    self.change_node_parent.left = None
                self.parent.left = self.change_node
                self.change_node.right = self.current_node.right
                self.change_node.left = self.change_node.left
            # case 3-2
            else:
                self.change_node = self.current_node.right
                self.change_node_parent = self.current_node.right
                while self.change_node.left != None:
                    self.change_node_parent = self.change_node
                    self.change_node = self.change_node.left
                if self.change_node.right != None:
                    self.change_node_parent.left = self.change_node.right
                else:
                    self.change_node_parent.left = None
                self.parent.right = self.change_node
                self.change_node.right = self.current_node.right
                self.change_node.left = self.current_node.left

        return True

 

 

 

 

📌 이진 탐색 트리 전체 코드

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

        
class NodeMgmt:
    def __init__(self, head):
        self.head = head
    
    def insert(self, value):
        self.current_node = self.head
        while True:
            if value < self.current_node.value:
                if self.current_node.left != None:
                    self.current_node = self.current_node.left
                else:
                    self.current_node.left = Node(value)
                    break
            else:
                if self.current_node.right != None:
                    self.current_node = self.current_node.right
                else:
                    self.current_node.right = Node(value)
                    break
    
    def search(self, value):
        self.current_node = self.head
        while self.current_node:
            if self.current_node.value == value:
                return True
            elif value < self.current_node.value:
                self.current_node = self.current_node.left
            else:
                self.current_node = self.current_node.right
        return False        
    
    def delete(self, value):
        # 삭제할 노드 탐색
        searched = False
        self.current_node = self.head
        self.parent = self.head
        while self.current_node:
            if self.current_node.value == value:
                searched = True
                break
            elif value < self.current_node.value:
                self.parent = self.current_node
                self.current_node = self.current_node.left
            else:
                self.parent = self.current_node
                self.current_node = self.current_node.right

        if searched == False:
            return False    

        # case1
        if  self.current_node.left == None and self.current_node.right == None:
            if value < self.parent.value:
                self.parent.left = None
            else:
                self.parent.right = None
        
        # case2
        elif self.current_node.left != None and self.current_node.right == None:
            if value < self.parent.value:
                self.parent.left = self.current_node.left
            else:
                self.parent.right = self.current_node.left
        elif self.current_node.left == None and self.current_node.right != None:
            if value < self.parent.value:
                self.parent.left = self.current_node.right
            else:
                self.parent.right = self.current_node.right        
        
        # case 3
        elif self.current_node.left != None and self.current_node.right != None:
            # case3-1
            if value < self.parent.value:
                self.change_node = self.current_node.right
                self.change_node_parent = self.current_node.right
                while self.change_node.left != None:
                    self.change_node_parent = self.change_node
                    self.change_node = self.change_node.left
                if self.change_node.right != None:
                    self.change_node_parent.left = self.change_node.right
                else:
                    self.change_node_parent.left = None
                self.parent.left = self.change_node
                self.change_node.right = self.current_node.right
                self.change_node.left = self.change_node.left
            # case 3-2
            else:
                self.change_node = self.current_node.right
                self.change_node_parent = self.current_node.right
                while self.change_node.left != None:
                    self.change_node_parent = self.change_node
                    self.change_node = self.change_node.left
                if self.change_node.right != None:
                    self.change_node_parent.left = self.change_node.right
                else:
                    self.change_node_parent.left = None
                self.parent.right = self.change_node
                self.change_node.right = self.current_node.right
                self.change_node.left = self.current_node.left

        return True

참고: http://ejklike.github.io/2018/01/09/traversing-a-binary-tree-1.html

 

Eunji Kim @ CAU - 파이썬을 사용한 이진 트리와 순회 알고리즘 구현 (1)

이진 트리 (binary tree)는 최대 두 개의 자식 노드를 가지는 트리 형태의 자료구조로서, 단순히 값을 저장하기 위한 용도로 사용되기 보다는 효율적인 탐색 혹은 정렬을 위해 사용된다. 이진 탐색

ejklike.github.io

 

 

✔️ 전체 코드 테스트 

# 0 ~ 999 숫자 중에서 임의로 100개를 추출해서, 이진 탐색 트리에 입력, 검색, 삭제
import random

# 0 ~ 999 중, 100 개의 숫자 랜덤 선택
bst_nums = set()
while len(bst_nums) != 100:
    bst_nums.add(random.randint(0, 999))
# print (bst_nums)

# 선택된 100개의 숫자를 이진 탐색 트리에 입력, 임의로 루트노드는 500을 넣기로 함
head = Node(500)
binary_tree = NodeMgmt(head)
for num in bst_nums:
    binary_tree.insert(num)
    
# 입력한 100개의 숫자 검색 (검색 기능 확인)
for num in bst_nums:
    if binary_tree.search(num) == False:
        print ('search failed', num)

# 입력한 100개의 숫자 중 10개의 숫자를 랜덤 선택
delete_nums = set()
bst_nums = list(bst_nums)
while len(delete_nums) != 10:
    delete_nums.add(bst_nums[random.randint(0, 99)])

# 선택한 10개의 숫자를 삭제 (삭제 기능 확인)
for del_num in delete_nums:
    if binary_tree.delete(del_num) == False:
        print('delete failed', del_num)

 

 

 

📌 이진 탐색 트리 시간 복잡도

✔️ 평균 시간 복잡도 : O(logn)

이는 트리가 균형잡혀 있을 때의 평균 시간 복잡도이다.

✔️ 최악의 시간 복잡도 : O(n)

다음과 같이 구성되어 있다면, 링크드 리스트와 같은 성능을 보여준다.