Tree - Binary Search Tree
이번에 설명드릴 자료구조의 형태는 완전 이진트리(Binary Search Tree)로, Tree 자료구조 중 가장 많이 사용되는 자료구조입니다! 물론 제일 쉽기도 해요~ 여기부터는 조금 더 추상적으로 이해해야 하는 부분이 많기 때문에 쉽사리 이해하기 힘들 수 있습니다. 하지만, 자료구조를 알지 못하면 프로그램을 작성하는 건 무리입니다. 자료구조의 강력한 기능과 구현력을 바탕으로 정상적으로 작동되는 프로그램이 만들어지기 때문입니다!
위의 형태로 구현되는 것이 완전 이진 트리입니다. 설명드리면, 노드의 왼쪽 하위 트리는 부모 노드보다 작은 노드가 포함되며 오른쪽 노드는 부모 노드보다 큰 노드가 포함됩니다! 당연히 추가되는 노드 또한 완전 이진트리의 형태를 갖추어야 하기 때문에 이러한 방식으로 삽입될 것입니다! 간단하게 트리에서 사용되는 용어를 설명드리자면,
노드 | 8,3,10 ... 등 각 객체가 노드가 됩니다. |
차수 | 부모 노드가 가지고 있는 자식 노드의 수 입니다. 8은 루트노드로서, 차수가 2이겠죠? |
리프 노드 | Leaf노드로 잎파리의 의미인데요, 위의 그림에서 1처럼 아래 자식노드를 하나도 가지고 있지 않은 노드입니다. 당연히 차수는 0이죠 |
내부 노드 | Branch노드로 자식을 가지고 있는 노드입니다. |
방향 간선 | Edge라고도 하는 부모 노드가 첫째, 자식 노드가 둘째 원소인 순서쌍이라고 볼 수 있습니다. |
경로 | Path는 이웃하는 두 노드가 앞이 부모, 뒤가 자식인 노드를 의미합니다. |
노드의 깊이 | 루트노드 (8)에서 내부노드(6)까지의 깊이는 2이겠죠? 네, 루트 노드와 현재 구하고자 하는 노드사이의 길이이다 |
노드의 레벨 | 루트 노드에서 자신까지 가는 경로의 길이 더하기 1을 하면됩니다. 루트 노드의 레벨 1인데요, 그렇다면, 위 그림의 Maximum Level은 4가 되겠죠!! |
어후 너무 많죠? 그냥 그렇구나하고 넘어가세요! 코드 짜다 보면 "아 이게 이 개념이었구나" 하게 되어있습니다!! 그렇다면 코드로 구현해볼까요? 함수의 크기가 조금 있는 편이기 때문에, 코드 블록별로 설명 드릴 텐데, 복잡하더라도 순서대로 코드 작성해보면서 따라오시면 금방 따라옵니다!!! 걱정 마세요 ㅎㅎㅎㅎ원래 어려운 형태니까요~
C++의 Class를 이용해 구현해보는 Tree(BST - Binary Search Tree)
1-1. 노드의 표현
노드의 표현은 너무 간단합니다. 어떤 걸 포함해야할까요? 당연히 노드는 부모 노드가 될 수 도 있고, 자식 노드도 될 수 있기 때문에, 왼쪽, 오른쪽 포인터 노드를 둘 다 가지고 있어야 합니다. 그리고 정수 값을 담을 변수가 들어갈 것입니다!!
1
2
3
4
|
struct Node{
Node *left, *right;
int value;
};
|
cs |
위처럼 구현해주시면 됩니다. 왼쪽과 오른쪽을 가리킬 수 있는 포인터 변수가 자식 노드를 가리킬 것입니다!!
1-2. 완전 이진 트리의 표현
조금 길 수 있어서 많이 끊어냈습니다. 코드 블록이 많이 생겨서 보는 게 귀찮은 것보다 하나하나 어떻게 구현되는지 확인하는 게 더 도움될 것 같아서요~
1-2-1. 클래스 구현부
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
|
class BinarySearchTree{
public:
BinarySearchTree():root(nullptr){};
~BinarySearchTree(){};
void addNode(int value);
bool searchValue(int value);
void removeNode(int value);
void show();
private:
Node *root;
Node *tail;
void inOrder(Node *current){
if(current != nullptr){
inOrder(current -> left);
cout << current -> value << endl;
inOrder(current -> right);
}
}
Node *searchMaxNode(Node *node){
if(node == NULL)
return NULL;
while(node -> right != NULL){
node = node -> right;
}
return node;
}
Node *removeSequence(Node *node, int _value);
};
|
cs |
루트노드와 꼬리 노드가 있는데, 뭐 사실 꼬리 노드는 해줘도 그만 ~ 안 해줘도 그만입니다. 루트 노드는 당연히 트리의 시작이 되는 노드이겠죠? 구현되는 멤버 함수는, 노드를 추가하는 함수, 탐색하는 함수, 제거하는 함수, 그리고 출력하는 함수는 inorder() 함수로 중위 순회의 의미를 가집니다. 중위 순회에 대해서 설명을 드리면,
만약 위와 같은 트리가 있다면, 깊이 우선 탐색을 실시하는데, 방향이 4 -> 2 -> 5 -> 1 -> 3의 순서로 왼쪽 노드부터 우선적으로 탐색하는 알고리즘입니다!!
그리고 searchMaxNode가 있는데, 아주 간단한 역할을 수행합니다. 최댓값을 가진 노드를 찾아서 반환하는데요, 잘 생각해보세요! 가장 큰 값은 오른쪽으로 계속 밀어 넣으니까 오른쪽으로만 탐색하면 아주 간단하게 탐색에 성공하겠죠?? ㅎㅎ 네 그렇습니다. 이제 노드를 추가하는 함수에 대해서 설명드리겠습니다.
1-2-2. 노드를 추가하는 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
void BinarySearchTree::addNode(int value){
Node *node = new Node();
Node *tmpRoot = nullptr;
node -> value = value;
if(root == nullptr)
root = node;
else{
Node *ptr = root;
while(ptr != nullptr){
tmpRoot = ptr;
if(node -> value < ptr -> value){
ptr = ptr -> left;
}else{
ptr = ptr -> right;
}
}
if(node -> value < tmpRoot -> value)
tmpRoot -> left = node;
else
tmpRoot -> right = node;
}
}
|
cs |
자 우선 함수의 인자로 새로들어올 데이터를 받습니다. 그리고 새로운 노드를 선언합니다. 만약 루트 노드가 nullptr이면, 루트 노드에 새로운 노드를 대입하고 동작을 종료합니다. 아니라면, 새로운 노드를 선언해 루트 노드를 복사합니다. 그리고 ptr노드가 nullptr이 아닐 때까지 반복하는데, 이때 tmpRoot에 ptr노드를 복사해줍니다. 그 이유는 아래 코드를 보면 알 수 있습니다. 현재 새로운 노드의 값이 ptr이 따라가고 있는 노드의 값보다 작다면 ptr은 왼쪽 노드로 순회하기 시작하고, 아니라면 오른쪽 노드로 순회할 것입니다. 그리고 더 이상 순회할 수 없을 때, tmpRoot의 값보다 작은 상황에선 왼쪽 자식 노드로 삽입하고, 큰 상황에서는 오른쪽 자식 노드로 정의하면서 함수 수행을 마칩니다! 어후 복잡한데, 천천히 읽어보시면 코드랑 대조해보시면 충분히 이해할 수 있습니다!!!
1-2-3. 노드를 제거하는 함수
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
|
Node * BinarySearchTree::removeSequence(Node *node, int _value){
if(node == nullptr) return node;
else if(node -> value > _value){
node -> left = removeSequence(node -> left ,_value);
}else if(node -> value < _value){
node -> right = removeSequence(node -> right, _value);
}else{
Node *ptr = node;
if(node -> right == nullptr && node -> left == nullptr){
delete node;
node = nullptr;
}else if(node -> right == nullptr){
node = node -> left;
delete ptr;
}else if(node -> left == nullptr){
node = node -> right;
delete ptr;
}else{
ptr = searchMaxNode(node -> left);
node -> value = ptr -> value;
node -> left = removeSequence(node -> left, ptr -> value);
}
}
return node;
}
void BinarySearchTree::removeNode(int value){
Node *ptr = root;
removeSequence(ptr, value);
}
|
cs |
자 우선 노드를 제거할 때는, removeNode()함수로 시작합니다. 제거하고자 하는 노드의 값을 함수의 인자로 받습니다. 그리고 ptr포인터 변수는 root를 대입받아 removeSequence() 함수의 인자로 대입됩니다. 아래 과정부터는 복잡해서 순서대로 설명드립니다.
- 만약 루트노드가 nullptr이면 그냥 현재의 루트노드를 반환하며 동작을 종료합니다.
- 만약 현재 노드의 값이 찾고자하는 값(findVal이라고 하겠음)보다 크다면 왼쪽 노드로 순회해 재귀합니다.
- 만약 findVal보다 작다면, 오른쪽 노드로 순회해 재귀합니다.
- 왼쪽 혹은 오른쪽으로 가서값을 찾았다면, 제거해주고 다른 노드와 연결해주는 작업을 진행합니다
- 새로운 노드를 선언해 node를 복사합니다.
- 만약 노드의 자식이 하나도 없는 경우에 현재 노드를 삭제하고 현재 노드를 nullptr로 초기화해주며 동작을 종료합니다.
- 만약 노드의 오른쪽이 nullptr인 경우에는 노드는 왼쪽 노드로 변경되고 현재 ptr노드는 메모리 해제되며 삭제됩니다.
- 만약 왼쪽 노드가 nullptr인 경우네느 노드를 오른쪽으로 변경해주고 현재 ptr노드를 메모리 해제하고 삭제합니다.
- 6,7,8의 상황이 아닌 경우에는 searchMaxNode()를 이용해 왼쪽을 탐색하고 현재 노드의 값에 탐색한 노드의 값을 대입하고 노드의 왼쪽을 다시 탐색해, 현재 값을 찾습니다. 만약 못찾는다면 현재 노드를 반환하고 프로그램의 동작은 종료됩니다.
후.. 설명만으로는 절대 이해할 수 없습니다! 직접 코드를 작성해보셔야 합니다. 어렵다고 포기하시면 안되구요!
1-2-4. 값을 찾는 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
bool BinarySearchTree::searchValue(int value){
Node *ptr= root;
Node *tmpRoot = nullptr;
while(ptr != nullptr){
if(ptr -> value == value){
cout << value << "Found" << endl;
return true;
}else if(ptr -> value > value)
ptr = ptr -> left;
else
ptr = ptr -> right;
}
cout << value << "not Found" << endl;
return false;
}
|
cs |
깊이우선 탐색을 실시해, 현재 찾고자 하는 값을 찾는 아주 간단한 함수입니다. 위 코드를 이해가 어느 정도 되셨다면 현재 함수를 이해할 수 있을 것입니다!!
2. main()
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
#include <iostream>
#include <string> using namespace std; int main(){ BinarySearchTree *BST = new BinarySearchTree();
BST -> addNode(1);
BST -> addNode(3);
BST -> addNode(6);
BST -> addNode(9);
BST -> addNode(13);
BST -> addNode(22);
BST -> addNode(17);
BST -> addNode(10);
BST -> addNode(2);
BST -> show(); cout << endl;
BST -> searchValue(4);
BST -> searchValue(17);
BST -> removeNode(22);
BST -> show();
cout << endl;
return 0;
}
RESULT >
1
2
3
6
9
10
13
17
22
4not Found
17Found
1
2
3
6
9
10
13
17
|
cs |
아주 정상적으로 동작하는 것을 볼 수 있습니다!! 뚝뚝 끊겨서 보기 싫으신 분이 있으면 다음부터는, github을 이용해 업로드하고 주소 참조를 해볼까 생각 중입니다!
봐주셔서 감사하고요~ 댓글로 문의, 피드백, 질문 뭐 다 받습니다!! 감사합니다@_@
'C++' 카테고리의 다른 글
[C++] static, const, explicit 제대로 알고 사용하기![1] (0) | 2020.12.18 |
---|---|
C++로 구현하는 자료구조!!이중연결리스트(Doubly Linked List)@@ (1) | 2020.11.30 |
C++로 구현하는 자료구조!!!연결리스트(LinkedList)@@ (2) | 2020.11.28 |
C++로 구현하는 자료구조!!!큐(Queue)편@@@ (0) | 2020.11.28 |
C++로 구현하는 자료구조!!!스택 편@@ (0) | 2020.11.27 |
댓글