Notice
Recent Posts
Recent Comments
Link
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Archives
Today
Total
관리 메뉴

Seung-MinJi

[자료 구조] 트리 본문

Computer Science

[자료 구조] 트리

지승민 2023. 7. 27. 09:27

대표적인 트리 구조

 

1. 트리 구조

트리 : Node와 branch를 이용해서 사이클을 이루지 않도록 구성한 데이터 구조

실제로 트리 중 이진트리 형태의 구조로, 탐색 알고리즘 구현을 위해 많이 사용

 

2. 알아둘 용어

Node : 트리에서 데이터를 저장하는 기본 요소(데이터와 다른 연결된 노드에 대한 Branch 정보 포함)

Root Node : 트리 맨 위에 있는 노드

Level :  최상위 노드를 Level 0으로 하였을 때 하위 Branch로 연결된 노드의 깊이를 나타냄

Parent Node : 어떤 노드의 다음 레벨에 연결된 노드

Child Node : 어떤 노드의 상위 레벨에 연결된 노드

Leaf Node : Child Node가 하나도 없는 노드

Sibling : 동일한 Parent Node를 가진 노드

Depth : 트리에서 Node가 가질 수 있는 최대 Level

 

3. 이진 트리와 이진 탐색트리 (binary Search Tree)

이진 트리 : 노드의 최대 Branch가 2인 트리

이진 탐색 트리 : 이진 트리에 다음과 같은 추가적인 조건이 있는 트리

- 왼쪽 노드는 해당 노드보다 작은 값, 오른쪽 노드는 해당 노드보다 큰 값을 가지고 있음

4. 자료 구조 이진 탐색 트리의 장점과 주요 용도

주요 용도 : 데이터 검색(탐색) - 탐색 속도를 개선할 수 있음

5. 파이썬 객체 지향 프로그래밍으로 링크드 리스트 구현하기

노드 클래스 만들기

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

head = Node(1)
BST = NodeMgmt(head)
BST.insert(2)
BST.insert(3)
BST.insert(0)
BST.insert(4)
BST.insert(8)

이진 탐색 트리 삭제

매우 복잡하기 때문에 경우를 나누어서 이해하는 것이 좋음

 

Leaf Node 삭제

Leaf Node : child node가 없는 node

삭제할 Node의 Parent Node가 삭제할 Node를 가리키지 않도록 한다.

 

child Node가 하나인 Node 삭제

삭제할 Node의 Parent Node가 삭제할 Node의 child Node를 가리키도록 한다.

 

child Node가 두개인 Node 삭제

1번 방법 : 삭제할 Node의 오른쪽 자식 중 가장 작은 값을 삭제할 Node의 Parent Node가 가리키도록 한다.

2번 방법 : 삭제할 Node의 왼쪽 자식 중 가장 큰 값을 삭제할 Node의 Parent Node가 가리키도록 한다.

- 삭제할 Node의 오른쪽 자식 선택

- 오른쪽 자식의 가장 왼쪽에 있는 Node를 선택

- 해당 Node를 삭제할 Node의 Parent Node의 왼쪽 branch가 가리키게 함

- 해당 Node의 왼쪽 Branch가 삭제할 Node의 왼쪽 Child Node를 가리키게 함

- 해당 Node의 오른쪽 Branch가 삭제할 Node의 오른쪽 Child Node를 가리키게함

- 만약 해당 Node가 오른쪽 child Node가 가지고 있었을 경우 해당 Node의 본래 Parent Node의 왼쪽 Branch가 해당 오른쪽 child Node를 가리키게 함

 

이진 탐색 트리 삭제 코드 구현과 분석

Case3-1 삭제할 노드가 Child Node를 두 개 가지고 있을 경우(삭제할 Node가 Parent Node 왼쪽에 있을 때

기본 사용 가능 전략

1. 삭제할 Node의 오른쪽 자식 중 가장 작은 값을 삭제할 Node의 Parent Node가 가리키도록 한다.

2. 삭제할 Node의 왼쪽 자식 중 가장 큰 값을 삭제할 Node의 Parent Node가 가리키도록 한다.

 

기본 사용 전략 중 1번 전략을 사용하여 코드를 구현

경우의 수가 또 다시 2가지 존재

1-1. 삭제할 Node가 Parent Node의 왼쪽에 있고 삭제할 Node의 오른쪽 자식 중 가장 작은 값을 가진 Node의 Child Node가 없을 때

1-2 삭제할 Node가 Parent Node의 왼쪽에 있고 삭제랗 Node의 오른쪽 자식 중 가장 작은 값을 가진 Node의 오른쪽에 Child Node가 있을때 가장 작은 값을 가진 Node의 Child Node가 왼쪽에 있을 경우는 없음, 왜냐하면 왼쪽 Node가 있다는 것은 해당 Node보다 더 작은 값을 가진 Node가 있다는 뜻이기 때문

 

Case3-2 삭제할 노드가 Child Node를 두 개 가지고 있을 경우(삭제할 Node가 Parent Node 오른쪽에 있을 때)

기본 사용 가능 전략

1. 삭제할 Node의 오른쪽 자식 중 가장 작은 값을 삭제할 Node의 Parent Node가 가리키도록 한다.

2. 삭제할 Node의 왼쪽 자식 중 가장 큰 값을 삭제할 Node의 Parent Node가 가리키도록 한다.

 

기본 사용 전략 중 1번 전략을 사용하여 코드를 구현

경우의 수가 또 다시 2가지 존재

2-1. 삭제할 Node가 Parent Node의 왼쪽에 있고 삭제할 Node의 오른쪽 자식 중 가장 작은 값을 가진 Node의 Child Node가 없을 때

2-2. 삭제할 Node가 Parent Node의 왼쪽에 있고 삭제할 Node의 오른쪽 자식 중 가장 작은 값을 가진 Node의 Child Node가 있을 때 가장 작은 값을 가진 Node의 Child Node가 왼쪽에 있을 경우는 없음, 왜냐하면 왼쪽 Node가 있다는 것은 해당 Node보다 더 작은 값을 가진 Node가 있다는 뜻이기 때문임

 

전체 코드

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

6. 이진 탐색 트리의 시간 복잡도와 단점

시간 복잡도(탐색시)

트리의 높이를 h라고 표기한다면 O(h)

n개의 노드를 가진다면 h = log2n에 가까우므로 시간 복잡도는 O(logn)

참고 : 빅오 표기법에서 logn에서의 log의 밑은 10이 아니라 2이다.

한번 실행시마다 50%의 실행할 수도 있는 명령을 제거한다는 의미, 즉 50%의 실행시간을 단축시킬 수 있다는 것을 의미함

이진 탐색 트리 단점

평균 시간 복잡도는 O(logn) 이지만

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

다음 예와 같이 구성되어 있을 경우 최악의 경우는 링크드 리스트 등과 동일한 성능를 보여줌 (O(n))

 

 

 

 

 

'Computer Science' 카테고리의 다른 글

[알고리즘] 버블 정렬  (0) 2023.09.08
[자료 구조] 힙  (0) 2023.08.15
[자료 구조]해쉬 테이블  (0) 2023.07.24
[알고리즘] 시간 복잡도 표현 방법  (0) 2023.07.24
[자료 구조] 링크드 리스트  (0) 2023.07.24