|
1 | | -## 이진 탐색 트리 BST(Binary Search Tree) |
| 1 | +# 이진 탐색 트리 (Binary Search Tree, BST) |
2 | 2 |
|
3 | | -**이진 탐색 트리의 목적은?** |
| 3 | +이진 탐색(binary search) ✚ 연결 리스트(linked list)를 결합한 이진트리 |
4 | 4 |
|
5 | | --> 이진 탐색 + 연결 리스트 |
| 5 | +이진 탐색 트리는 각 노드가 최대 두 개의 자식 노드를 가지는 트리 자료 구조 |
6 | 6 |
|
7 | | -- 이진 탐색 |
8 | | - - **탐색에 소요되는 시간 복잡도는 O(logN)** |
9 | | - - 하지만 삽입, 삭제가 불가능. |
10 | | -- 연결 리스트 |
11 | | - - **삽입, 삭제의 시간 복잡도는 O(1)** |
12 | | - - 하지만 탐색하는 시간 복잡도는 O(N) |
13 | | -- 이 두 가지를 합하여 장점을 모두 얻기 위해 고안된 것이 `이진 탐색 트리` |
14 | | -- 즉, 효율적인 탐색 능력을 가지고 자료의 삽입, 삭제도 가능하게 만드는 것이다. |
| 7 | + |
15 | 8 |
|
16 | | - |
17 | 9 |
|
18 | | -### [특징] |
| 10 | +## 특징 |
19 | 11 |
|
20 | | -- 이진 트리의 일종으로 이진 탐색 트리에는 데이터를 저장하는 규칙이 있다. |
21 | 12 |
|
22 | | -- 이진 탐색 트리의 노드에 저장된 키는 유일하다. |
23 | | - 1. 루트 노드의 키가 왼쪽 서브 트리를 구성하는 어떠한 노드의 키보다 크다. |
24 | | - 2. 루트 노드의 키가 오른쪽 서브 트리를 구성하는 어떠한 노드의 키보다 작다. |
25 | | - 3. 왼쪽과 오른쪽 서브 트리도 이진 탐색 트리이다. |
26 | | -- 탐색의 시간 복잡도는 O(logN)이다. 최악의 경우, 편향 트리가 되어 O(N)이 될 수도 있다. |
27 | | -- 이진 탐색 트리의 순회는 **중위 순회**(in order) 방식이다.(왼쪽 - 루트 - 오른쪽) |
28 | | -- 중위 순회로 **정렬된 순서**를 읽을 수 있다. |
29 | | -- 중복된 노드가 없어야 한다. |
| 13 | +노드의 왼쪽 하위 트리에는 노드의 키보다 작은 키가있는 노드만 포함 |
30 | 14 |
|
31 | | -**중복이 없어야 하는 이유는??** |
| 15 | +노드의 오른쪽 하위 트리에는 노드의 키보다 큰 키가있는 노드 만 포함 |
32 | 16 |
|
33 | | -검색을 목적으로 하는 자료구조인데, 굳이 중복이 많은 경우에 이 트리를 사용해 검색 속도를 느리게 할 필요가 없다. 트리에 삽입하는 것보다 노드에 count를 가지게 하여 처리하는 것이 훨씬 효율적이다. |
| 17 | +왼쪽 및 오른쪽 하위 트리도 각각 이진 검색 트리여야함 |
34 | 18 |
|
| 19 | +중복된 키를 허용하지 않음 |
35 | 20 |
|
| 21 | +### 기본 연산 |
36 | 22 |
|
37 | | -**[BST 핵심 연산]** |
| 23 | +#### 검색 (Search) |
| 24 | +이진 탐색 트리에서 특정 요소의 위치를 찾음 |
38 | 25 |
|
39 | | -- 검색 |
40 | | -- 삽입 |
41 | | -- 삭제 |
42 | | -- 트리 생성 |
43 | | -- 트리 삭제 |
44 | 26 |
|
| 27 | +1. 루트에서 시작 |
| 28 | +2. 검색 값을 루트와 비교하여 루트보다 작으면 왼쪽에 대해 재귀하고 크다면 오른쪽으로 재귀 |
| 29 | +3. 일치하는 값을 찾을 때까지 절차를 반복 |
| 30 | +4. 검색 값이 없으면 null을 반환 |
45 | 31 |
|
46 | 32 |
|
47 | | -**[시간 복잡도]** |
| 33 | + |
48 | 34 |
|
49 | | -- 균등 트리 : 노드 개수가 N개일 때, O(logN) |
50 | | -- 편향 트리 : 노드 개수가 N개일 때, O(N) |
| 35 | +```C |
51 | 36 |
|
52 | | -> 삽입, 검색, 삭제 시간 복잡도는 트리의 Depth에 비례. |
| 37 | +struct node* search (struct node* root, int key) |
| 38 | +{ |
| 39 | +// root값이 null이거나 key값과 같다면 종료한다. |
| 40 | + if (root == NULL || root->data == key) |
| 41 | + return root; |
53 | 42 |
|
| 43 | +// key가 root->data 보다 작으면 왼쪽 서브트리로 재귀한다. |
| 44 | + if (root->data > key) |
| 45 | + return search(root->left, key) |
54 | 46 |
|
| 47 | +// key가 root->data 보다 크면 오른쪽 서브트리로 재귀한다. |
| 48 | + return search(root->left, key) |
| 49 | +} |
55 | 50 |
|
56 | | -삭제의 3가지 case |
| 51 | +``` |
57 | 52 |
|
58 | | -1. 자식이 없는 단말 노드일 때 -> 그냥 삭제 |
59 | | -2. 자식이 1개인 노드일 때 -> 지워진 노드에 자식을 올리기 |
60 | | -3. 자식이 2개인 노드일 때 -> 오른쪽 자식 노드에서 가장 작은 값 or 왼쪽 자식 노드에서 가장 큰 값 올리기 |
61 | 53 |
|
| 54 | +#### 삽입 |
62 | 55 |
|
| 56 | +이진 검색트리에 데이터를 삽입하는 작업 (중복은 형용하지않음) |
63 | 57 |
|
64 | | -편향된 트리(정렬된 상태 값을 트리로 만들면 한쪽으로 뻗음)는 시간 복잡도가 O(N) 이므로 트리를 사용할 이유가 사라진다. 이를 바로 잡도록 도와주고 개선된 트리는 AVL Tree, RedBlack Tree가 있다. |
| 58 | +새 키는 항상 리프 노드에 삽입 |
65 | 59 |
|
| 60 | +1.Root에서 시작 |
| 61 | +2.삽입 값을 루트와 비교합니다. 루트보다 작으면 왼쪽으로 재귀하고 크다면 오른쪽으로 재귀 |
| 62 | +3.리프 노드에 도달한 후 노드보다 크다면 오른쪽에 작다면 왼쪽에 삽입 |
66 | 63 |
|
| 64 | + |
67 | 65 |
|
68 | | -**[이진 트리의 순회 방법]** |
| 66 | +```C |
69 | 67 |
|
70 | | -1. 전위 순회(Pre Order) : 루트 -> 왼쪽 -> 오른쪽 |
71 | | -2. 중위 순회(In Order) : 왼쪽 -> 루트 -> 오른쪽 |
72 | | -3. 후위 순회(Post Order) : 왼쪽 -> 오른쪽 -> 루트 |
| 68 | +struct node { |
| 69 | + int data; |
| 70 | + struct node *left, *right; |
| 71 | +}; |
73 | 72 |
|
74 | | ---- |
| 73 | +// 새로운 BST node 생성 |
| 74 | +struct node* newNode (int key) { |
| 75 | + struct node* temp = (struct *node)malloc(sizeof(struct node)); |
| 76 | + temp->data = key; |
| 77 | + temp->left = NULL; |
| 78 | + temp->right = NULL; |
| 79 | + return temp; |
| 80 | +} |
75 | 81 |
|
76 | | -### 참조 |
| 82 | +struct node* insert(struct node *root, int key) { |
| 83 | + // 트리가 비어있다면 새로운 노드를 만든다. |
| 84 | + if (root == NULL) |
| 85 | + return newNode(key); |
77 | 86 |
|
78 | | -정리가 너무 잘되어있어 이 부분은 참조하였습니다. |
| 87 | + // 루트값보다 크면 오른쪽으로 재귀하고, 작다면 왼쪽으로 재귀한다. |
| 88 | + if (key > root->data) |
| 89 | + root->right = insert(root->right, key); |
| 90 | + else if (key < root->data) |
| 91 | + root->left = insert(root->left, key); |
| 92 | + // 같은 값을 가지고 있는 경우 삽입을 하지 않는다.(중복 불가) |
| 93 | + return root; |
| 94 | +} |
79 | 95 |
|
80 | | -https://github.com/WooVictory/Ready-For-Tech-Interview/blob/master/Data%20Structure/%5BData%20Structure%5D%20%EC%9D%B4%EC%A7%84%20%ED%83%90%EC%83%89%20%ED%8A%B8%EB%A6%AC.md |
| 96 | +``` |
| 97 | + |
| 98 | +#### 삭제 |
| 99 | + |
| 100 | + |
| 101 | +이진 검색 트리에서 특정 노드를 삭제 |
| 102 | + |
| 103 | +이진 검색 트리에서 노드를 삭제하는 세 가지 상황이 있음 |
| 104 | + |
| 105 | +**1. 삭제할 노드가 리프노드인 경우** |
| 106 | + |
| 107 | +이 경우 노드를 삭제하기만 하면 됨 |
| 108 | + |
| 109 | + |
| 110 | + |
| 111 | + |
| 112 | +**2.삭제할 노드에 자식이 하나만 있는 경우** |
| 113 | + |
| 114 | +노드를 삭제하고 자식 노드를 삭제된 노드의 부모에 직접 연결 |
| 115 | + |
| 116 | + |
| 117 | + |
| 118 | +**삭제할 노드에 자식이 둘 있는 경우** |
| 119 | + |
| 120 | +자식이 둘 있는 경우 successor 노드를 찾는 과정이 추가 |
| 121 | + |
| 122 | + |
| 123 | + |
| 124 | +#### surrcessor 노드란? |
| 125 | + |
| 126 | +right subtree에 최소값 |
| 127 | + |
| 128 | +즉, inorder 순회에서 다음 노드를 말함 |
| 129 | + |
| 130 | +1. 삭제할 노드를 찾음 |
| 131 | +2. 삭제할 노드의 successor 노드를 찾음 |
| 132 | +3. 삭제할 노드와 successor 노드의 값을 바꿈 |
| 133 | +4. successor 노드를 삭제 |
| 134 | + |
| 135 | + |
| 136 | + |
| 137 | + |
| 138 | + |
| 139 | +```C |
| 140 | + |
| 141 | +struct node { |
| 142 | + int data; |
| 143 | + struct node *left, *right; |
| 144 | +}; |
| 145 | + |
| 146 | +// 노드의 최소값을 가져오는 함수 |
| 147 | +struct node* minValueNode (struct node* node){ |
| 148 | + struct node* current = node; |
| 149 | + |
| 150 | + while(current && current->left != NULL) |
| 151 | + current = current->left; |
| 152 | + |
| 153 | + return current; |
| 154 | +} |
| 155 | + |
| 156 | +struct node* deleteNode (struct node* root, int key) { |
| 157 | +// base case |
| 158 | + if(root == NULL) |
| 159 | + return root; |
| 160 | +// 삭제할 노드를 찾는다. |
| 161 | + if (key < root->data) |
| 162 | + root->left = deleteNode(root->left,key); |
| 163 | + |
| 164 | + else if (key > root->data) |
| 165 | + root->right = deleteNode(root->right, key); |
| 166 | + |
| 167 | +// 삭제할 노드를 찾은 경우 |
| 168 | + else { |
| 169 | + struct node* temp; |
| 170 | +// 노드에 자식이 하나 이거나 없는 경우 |
| 171 | + if (root->left == NULL) { |
| 172 | + temp = root->right; |
| 173 | + free(root); |
| 174 | + return temp; |
| 175 | + } |
| 176 | + else if (root->right == NULL) { |
| 177 | + temp = root->left; |
| 178 | + free(root); |
| 179 | + return temp; |
| 180 | + } |
| 181 | + |
| 182 | +// 노드에 자식이 둘 있는 경우 |
| 183 | +// successor 노드를 찾는다. |
| 184 | + temp = minValueNode(root->right); |
| 185 | +// successor 노드 키와 삭제할 노드 키를 바꾼다. |
| 186 | + root->key = temp->key; |
| 187 | +// 노드를 삭제한다. |
| 188 | + root->right = deleteNode(root->right, temp->key); |
| 189 | + } |
| 190 | + return root; |
| 191 | +} |
| 192 | + |
| 193 | +``` |
0 commit comments