본문 바로가기
이론/알고리즘

알고리즘 스터디 3회차 - 이진트리(Binary Tree) 구현

by 유세지 2018. 3. 9.


- 이진탐색트리 구현

 구글링 하는데 90% + 옮겨 적는데 8% + 합치는데 2% 정도의 비율로 이진 트리를 구현하였다.


 몇 군데의 블로그에 올려진 이진트리 소스들을 보고 짜맞춰서 순회 기능 + 추가 및 삭제 기능을 가진 이진 트리를 구현해보았다. 책은 어차피 라이브러리에 구현되어있는 이진 트리를 쓰고, 심지어 언어도 파이썬이라 개념만 정리하고 나머지는 구글링에 의존하여 소스를 짰다. 사실 짰다는 말보다는 짜맞췄다라는 말이 더 어울리지만 "이해했으면 된거지..." 라고 생각하며 정리해본다.


이진 트리 구현하랬는데 이진탐색트리를 해버렷네 ㅎ



 이진탐색트리란?


 효율적인 탐색 작업을 위해 만들어진 자료구조로 몇 가지 특징을 가지고 있다.


 1. 모든 원소는 서로 다른 유일한 키를 가진다.

 2. 왼쪽 서브트리의 원소들은 그 루트의 값보다 작다.

 3. 오른쪽 서브트리에 있는 원소들은 그 루트의 값보다 크다.

 4. 각 서브트리들 또한 이진트리이다.


 간단히 모양을 살펴보면


 

 이런 식이다. 5를 루트 노드로 잡고 1 2 3 4 6 7 8 9 순으로 값을 넣은 결과이다. 이 이진트리는 밑의 코드에서 기본으로 갖고 있는 트리이기도 하다.

 


 순회 (Traversal)


 순회란 트리 구조에서 각각의 노드를 정확히 한 번만, 체계적인 방법으로 방문하는 과정을 말한다. (위키백과 트리 순회 항목)

 방문하는 노드의 순서에 따라 크게 4가지로 분류되는데


 1. 전위 순회 (Preorder Traversal, NLR)

 노드 n에 방문했을때 n을 먼저 방문한다. 그 다음에 n의 왼쪽 자식노드로 순회를 계속한다. n의 왼쪽 서브트리의 모든 노드들을 방문한 후에는 n의 오른쪽 서브트리의 모든 후손 노드들을 방문한다.


 2. 중위 순회 (Inorder Traversal, LNR)

 노드 n에 도착하면 n의 방문을 보류하고 n의 왼쪽 서브트리로 순회를 진행한다. 즉, 왼쪽 서브트리의 모든 노드들을 방문한 후에 n을 방문한다. n을 방문한 후에는 n의 오른쪽 서브트리를 같은 방식으로 방문한다.


 3. 후위 순회 (Postorder Traversal, LRN)

 노드 n에 도착하면 n의 방문을 보류하고 n의 왼쪽 서브트리로 순회를 진행한다. n의 왼쪽 서브트리를 방문한 후에는 n의 오른쪽 서브트리를 같은 방식으로 방문한다. 그리고 마지막에 n을 방문한다.


 4. 레벨 순회 (Level-order Traversal)

 루트가 있는 최상위 레벨부터 시작하여 각 레벨마다 좌에서 우로 노드들을 방문한다.



 이렇게 4가지가 있는데 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
// BinaryTree with LinkedList
 
int size = 0;
 
typedef struct Tree_node *tree_pointer;
 
typedef struct Tree_node {
    int key;
    tree_pointer left;
    tree_pointer right;
}tree_node;
 
typedef struct binarytree {
    Tree_node *Root;
}binarytree;
 
tree_pointer CreateNode(int Key);
 
void Preorder(tree_pointer ptr) {
    if (ptr != NULL) {
        printf(" %d ", ptr->key);
        Preorder(ptr->left);
        Preorder(ptr->right);
    }
}
 
void Inorder(tree_pointer ptr) {
    if (ptr != NULL) {
        Inorder(ptr->left);
        printf(" %d ", ptr->key);
        Inorder(ptr->right);
    }
}
 
void Postorder(tree_pointer ptr) {
    if (ptr != NULL) {
        Postorder(ptr->left);
        Postorder(ptr->right);
        printf(" %d ", ptr->key);
    }
}
 
void NodeCount(tree_pointer ptr) {
    if (ptr != NULL) {
        size++;
        NodeCount(ptr->left);
        NodeCount(ptr->right);
    }
}
 
bool Is_There(binarytree *Target_Tree, int Item) {
    tree_node *cur = Target_Tree->Root;
    size = 0;
    NodeCount(cur);
    for(int i = 0; i<size; i++) {
        if (cur->key == Item) {
            return true;
        }
        if (cur->key < Item) {
            if (cur->left == NULL)
                return false;
            cur = cur->right;
        }
        else {
            if (cur->left == NULL)
                return false;
            cur = cur->left;
        }
    }
    return false;
}
 
void Tree_init(binarytree *Target_tree) {
    Target_tree->Root = NULL;
}
 
void Add_Node(binarytree *Target_tree, int Item) {
    tree_node *NewNode = (tree_node *)malloc(sizeof(tree_node));
    if (Target_tree->Root == NULL)
    {
        Target_tree->Root = NewNode;
        NewNode->left = NULL;
        NewNode->right = NULL;
        NewNode->key = Item;
        return;
    }
    if (Is_There(Target_tree, Item)) {
        printf("%d is exist already.\n", Item);
        return;
    }
    NewNode->left = NULL;
    NewNode->right = NULL;
    NewNode->key = Item;
    tree_node *cur = Target_tree->Root;
    while (1) {
        if (cur->key < Item)
        {
            if (cur->right == NULL) {
                cur->right = NewNode;
                return;
            }
            cur = cur->right;
        }
        else {
            if (cur->left == NULL) {
                cur->left = NewNode;
                return;
            }
            cur = cur->left;
        }
    }
}
 
int Delete_Node(binarytree *Target_tree, int Item) {
    tree_node *cur2 = NULL;
    tree_node *cur = Target_tree->Root;
    tree_node *par_parent = NULL;
    tree_node *parent = NULL;
    tree_node *child = NULL;
    tree_node *left_temp = NULL;
    int key_return = 0;
    if (!Is_There(Target_tree, Item)) {
        printf("%d is not exist\n", Item);
        return 0;
    }
    while (cur->key != Item) {
        if (Item > cur->key) {
            par_parent = parent;
            parent = cur;
            cur = cur->right;
        }
        else {
            par_parent = parent;
            parent = cur;
            cur = cur->left;
        }
    }
 
    // 자식노드가 없을 경우
    if (cur->left == NULL && cur->right == NULL) {
        key_return = cur->key;
        if (parent->left == cur) parent->left = NULL;
        if (parent->right == cur) parent->right = NULL;
        free(cur);
        return key_return;
    }
    // 자식노드가 하나 있을 경우
    if (cur->left == NULL || cur->right == NULL) {
        child = (cur->left != NULL) ? cur->left : cur->right;
 
        if (parent->left == cur) parent->left = child;
        else parent->right = child;
        key_return = cur->key;
        free(cur);
        return key_return;
    }
    // 자식노드가 둘 있을 경우
    if (cur->left != NULL && cur->right != NULL) {
        cur2 = cur;
        cur2 = cur2->right;
        if (cur2->left == NULL) {
            left_temp = cur->left;
            child = cur2;
            if (parent->right == cur) {
                parent->left = child;
                child->left = left_temp;
            }
            else {
                if (parent->left == cur) {
                    parent->left = child;
                    child->left = left_temp;
                }
                key_return = cur->key;
                free(cur);
                return key_return;
            }
            while (cur2->left != NULL) {
                parent = cur2;
                cur2 = cur2->left;
            }
            parent->left = NULL;
            cur->key = cur2->key;
            free(cur2);
        }
        return 0;
    }
}
 
int main(void) {
    int com, key, putin = 0;
 
    tree_pointer travelNode;
    tree_pointer one = CreateNode(1);
    tree_pointer two = CreateNode(2);
    tree_pointer three = CreateNode(3);
    tree_pointer four = CreateNode(4);
    tree_pointer five = CreateNode(5);
    tree_pointer six = CreateNode(6);
    tree_pointer seven = CreateNode(7);
    tree_pointer eight = CreateNode(8);
    tree_pointer nine = CreateNode(9);
 
    travelNode = (tree_node*)malloc(sizeof(tree_node));
 
    five->left = one;
    one->right = two;
    two->right = three;
    three->right = four;
 
    five->right = six;
    six->right = seven;
    seven->right = eight;
    eight->right = nine;
 
    travelNode = five;
 
    binarytree *list = (binarytree *)malloc(sizeof(binarytree));
    list->Root = five;
 
    while (1) {
        printf("\n ::::: Binary Tree :::::\n\n ::::: Travel command >>>\n [1] Preorder Traversal\n [2] Inorder Traversal\n [3] Postorder Traversal\n\n ::::: Modifying command >>>\n [4] Add Node\n [5] Delete Node\n [6] DELETE ALL NODE\n\n ::::: Select number :::::\n\n");
        scanf_s("%d"&com);
 
        switch (com) {
        case 1:
            printf("******\n\n");
            Preorder(travelNode);
            printf("\n\n******\n\n");
            break;
 
        case 2:
            printf("******\n\n");
            Inorder(travelNode);
            printf("\n\n******\n\n");
            break;
 
        case 3:
            printf("******\n\n");
            Postorder(travelNode);
            printf("\n\n******\n\n");
            break;
        case 4:
            printf("****** Please enter new item\n\n");
            scanf_s("%d"&key);
            Add_Node(list, key);
            printf("\n\n******\n\n");
            break;
        case 5:
            printf("****** Please enter delete item\n\n");
            scanf_s("%d"&key);
            Delete_Node(list, key);
            printf("\n\n******\n\n");
            break;
        case 6:
            printf("****** Delete All Node\n\n");
            Tree_init(list);
            break;
        default:
            printf("undefined command\n");
            break;
        }
    }
}
 
tree_pointer CreateNode(int Key) {
    tree_pointer newNode;
    newNode = (tree_node*)malloc(sizeof(tree_node));
 
    newNode->left = NULL;
    newNode->right = NULL;
    newNode->key = Key;
 
    return newNode;
}
cs



소스 코드에 주석이 별로 없다. 블로그에 정리하면서 다시 달며 공부할 목적으로 일부러 있는 것도 거의 다 뺐다.


#include <stdio.h>

#include <stdlib.h>

#include <conio.h>

// BinaryTree with LinkedList


/*

 헤더 파일 부분. conio.h는 굳이 필요없긴 하다. 디버깅 할 때 쓰다가 지우는걸 깜빡했다.

*/


int size = 0; // 이진 트리가 가진 노드의 총 갯수를 세기 위해 전역 변수로 선언해주었다. 이 변수는 밑의 NodeCount 함수에서 따로 불러 사용할 예정이다.


typedef struct Tree_node *tree_pointer;


typedef struct Tree_node {

int key;

tree_pointer left;

tree_pointer right;

}tree_node;


/*

Tree_node 라는 구조체를 선언해주었다. 이 구조체는 이진 트리의 노드 역할을 수행할 것이기 때문에 실제 정보값인 key 와 left, right 라는 두 개의 포인터 변수를 갖는다.

*/


typedef struct binarytree {

Tree_node *Root;

}binarytree;


/*

binarytree 라는 구조체를 선언해주었다. 이 구조체는 이진 트리 자체가 될 것이기 때문에 포인터 변수 Root를 가진다.

*/


tree_pointer CreateNode(int Key); // 노드를 수동으로 생성해 주는 역할을 할 함수이다. 지금 생각났는데 밑에 Add_Node 함수가 있으니 이건 굳이 없어도 될 것 같다.


void Preorder(tree_pointer ptr) {

if (ptr != NULL) {

printf(" %d ", ptr->key);

Preorder(ptr->left);

Preorder(ptr->right);

}

}


/*

전위 순회 함수 Preorder 이다. NLR에 따라 [현재 노드 > 왼쪽 자식 > 오른쪽 자식] 을 재귀 함수를 이용하여 프린트 해준다.

*/


void Inorder(tree_pointer ptr) {

if (ptr != NULL) {

Inorder(ptr->left);

printf(" %d ", ptr->key);

Inorder(ptr->right);

}

}


/*

중위 순회 함수 Inorder 이다. LNR에 따라 [왼쪽 자식 > 현재 노드 > 오른쪽 자식] 을 재귀 함수를 이용하여 프린트 해준다. Preorder 와는 순서만 바뀌었다.

*/

void Postorder(tree_pointer ptr) {

if (ptr != NULL) {

Postorder(ptr->left);

Postorder(ptr->right);

printf(" %d ", ptr->key);

}

}


/*

후위 순회 함수 Postorder 이다. LRN에 따라 [ 왼쪽 자식 > 오른쪽 자식 > 현재 노드] 을 재귀 함수를 이용하여 프린트 해준다. 마찬가지로 순서만 바뀌었다.

*/

void NodeCount(tree_pointer ptr) {

if (ptr != NULL) {

size++;

NodeCount(ptr->left);

NodeCount(ptr->right);

}

}

/*

이진 트리가 몇 개의 노드를 갖고 있는지 세어 주는 함수이다. 전역 변수 size를 그냥 늘려버리기 때문에 사용할때 미리 size를 0으로 초기화 해 줄 필요가 있다.

*/

bool Is_There(binarytree *Target_Tree, int Item) {

tree_node *cur = Target_Tree->Root;

size = 0;

NodeCount(cur);

for(int i = 0; i<size; i++) {

if (cur->key == Item) {

return true;

}

if (cur->key < Item) {

if (cur->left == NULL)

return false;

cur = cur->right;

}

else {

if (cur->left == NULL)

return false;

cur = cur->left;

}

}

return false;

}

/*

노드를 삭제할때 그 노드가 존재하는지 아닌지를 검사하기 위한 함수이다. NodeCount를 이용해 노드의 총 갯수를 세고, 그 이상 탐색을 해도 발견되지 않거나 탐색 중 더 이상 자식이 없게되면 false를 리턴하게 된다.

*/

void Tree_init(binarytree *Target_tree) {

Target_tree->Root = NULL;

}

/*

이진 트리를 새로 만드는 함수이다.

하지만 sudo rm -rf / 시키는 용도로만 쓰이고 있다.

*/


void Add_Node(binarytree *Target_tree, int Item) {

tree_node *NewNode = (tree_node *)malloc(sizeof(tree_node));

if (Target_tree->Root == NULL)

{

Target_tree->Root = NewNode;

NewNode->left = NULL;

NewNode->right = NULL;

NewNode->key = Item;

return;

}

if (Is_There(Target_tree, Item)) {

printf("%d is exist already.\n", Item);

return;

}

NewNode->left = NULL;

NewNode->right = NULL;

NewNode->key = Item;

tree_node *cur = Target_tree->Root;

while (1) {

if (cur->key < Item)

{

if (cur->right == NULL) {

cur->right = NewNode;

return;

}

cur = cur->right;

}

else {

if (cur->left == NULL) {

cur->left = NewNode;

return;

}

cur = cur->left;

}

}

}

/*

이진 트리에 노드를 추가하는 함수이다. 새로 만든 NewNode를 인자로 받은 이진 트리의 루트 위치로 옮기고, 이진 트리의 정의에 따라 탐색을 시작하여 적절한 위치에 노드를 연결시켜준다.

*/

int Delete_Node(binarytree *Target_tree, int Item) {

tree_node *cur2 = NULL;

tree_node *cur = Target_tree->Root;

tree_node *par_parent = NULL;

tree_node *parent = NULL;

tree_node *child = NULL;

tree_node *left_temp = NULL;

int key_return = 0;

if (!Is_There(Target_tree, Item)) {

printf("%d is not exist\n", Item);

return 0;

}

while (cur->key != Item) {

if (Item > cur->key) {

par_parent = parent;

parent = cur;

cur = cur->right;

}

else {

par_parent = parent;

parent = cur;

cur = cur->left;

}

}


// 자식노드가 없을 경우

if (cur->left == NULL && cur->right == NULL) {

key_return = cur->key;

if (parent->left == cur) parent->left = NULL;

if (parent->right == cur) parent->right = NULL;

free(cur);

return key_return;

}

// 자식노드가 하나 있을 경우

if (cur->left == NULL || cur->right == NULL) {

child = (cur->left != NULL) ? cur->left : cur->right;


if (parent->left == cur) parent->left = child;

else parent->right = child;

key_return = cur->key;

free(cur);

return key_return;

}

// 자식노드가 둘 있을 경우

if (cur->left != NULL && cur->right != NULL) {

cur2 = cur;

cur2 = cur2->right;

if (cur2->left == NULL) {

left_temp = cur->left;

child = cur2;

if (parent->right == cur) {

parent->left = child;

child->left = left_temp;

}

else {

if (parent->left == cur) {

parent->left = child;

child->left = left_temp;

}

key_return = cur->key;

free(cur);

return key_return;

}

while (cur2->left != NULL) {

parent = cur2;

cur2 = cur2->left;

}

parent->left = NULL;

cur->key = cur2->key;

free(cur2);

}

return 0;

}

}

/*

이건 코드가 좀 길고 한 눈에 보기 어려워서 미리 주석을 넣어놨다. 물론 저 코드를 찾은 블로그에도 표시되어 있는 주석이다.


1. 삭제하려는 노드에 자식 노드가 없을 경우

부모 노드의 연결을 해제해버리고 (parent->left(or right) = NULL) 해당되는 노드의 메모리를 free() 함수를 통해 날려준다.


2. 삭제하려는 노드에 자식 노드가 하나 있을 경우

자식 노드가 left인지, right인지 먼저 검사한뒤 부모 노드에게 연결해준다. 그리고 해당 노드에 할당 된 메모리를 free()를 통해 해제한다.


3. 삭제하려는 노드에 자식 노드가 둘 있을 경우

삭제 작업을 위해 만든 노드(cur2)에 현재 노드(cur)의 오른쪽 자식노드를 덮어 씌운 후, 임시 노드(left_temp)에 현재 노드의 왼쪽 자식노드를 덮어 씌운다. cur2가 왼쪽 자식노드를 갖고 있지 않다면 left_temp에 cur의 왼쪽 자식노드를 덮어 씌우고, child를 cur2으로 만든다. cur이 parent의 오른쪽 자식노드라면 parent의 왼쪽 자식노드를 child(cur2)로 연결해주고, child의 왼쪽 자식노드를 left_temp로 연결시켜준다. cur이 parent의 왼쪽 자식노드라면 그 자리를 child에게 연결시키고, child의 왼쪽 자식노드를 left_temp로 만들어준다. 그 후 할당된 메모리를 해제시키고, cur2가 왼쪽 자식노드를 갖지 않을때까지 cur2의 왼쪽 자식노드를 parent로 연결시켜준다. 마지막으로 parent의 왼쪽 자식노드를 NULL로 만들고, cur의 키 값을 cur2의 키 값으로 만들어준다. 자고 일어나서 그림으로 다시 설명해야겠다 글로 쓰니까 무슨 말인지 내가 봐도 모르겠다.

*/


int main(void) { // 메인이다

int com, key, putin = 0; // com은 메뉴를 선택할때 쓰이고, key는 Add_Node나 Delete_Node에서 키 값을 입력할때 쓰인다. putin은 뭔지 모르겠다. 지워도 된다. 


tree_pointer travelNode;

tree_pointer one = CreateNode(1);

tree_pointer two = CreateNode(2);

tree_pointer three = CreateNode(3);

tree_pointer four = CreateNode(4);

tree_pointer five = CreateNode(5);

tree_pointer six = CreateNode(6);

tree_pointer seven = CreateNode(7);

tree_pointer eight = CreateNode(8);

tree_pointer nine = CreateNode(9);


travelNode = (tree_node*)malloc(sizeof(tree_node));


five->left = one;

one->right = two;

two->right = three;

three->right = four;


five->right = six;

six->right = seven;

seven->right = eight;

eight->right = nine;


/*

일단 함수를 만들었으니 쓰긴 하는데 괜한짓했다. Add_Node로 추가하는게 훨씬 편하다. 

*/

travelNode = five;


binarytree *list = (binarytree *)malloc(sizeof(binarytree));

list->Root = five;


/*

순회에 쓰일 노드(travelNode)를 이진 트리의 루트 노드(five)로 지정해준다.  

*/


while (1) {

printf("\n ::::: Binary Tree :::::\n\n ::::: Travel command >>>\n [1] Preorder Traversal\n [2] Inorder Traversal\n [3] Postorder Traversal\n\n ::::: Modifying command >>>\n [4] Add Node\n [5] Delete Node\n [6] DELETE ALL NODE\n\n ::::: Select number :::::\n\n");

scanf_s("%d", &com);


switch (com) {

case 1:

printf("******\n\n");

Preorder(travelNode);

printf("\n\n******\n\n");

break;


case 2:

printf("******\n\n");

Inorder(travelNode);

printf("\n\n******\n\n");

break;


case 3:

printf("******\n\n");

Postorder(travelNode);

printf("\n\n******\n\n");

break;

case 4:

printf("****** Please enter new item\n\n");

scanf_s("%d", &key);

Add_Node(list, key);

printf("\n\n******\n\n");

break;

case 5:

printf("****** Please enter delete item\n\n");

scanf_s("%d", &key);

Delete_Node(list, key);

printf("\n\n******\n\n");

break;

case 6:

printf("****** Delete All Node\n\n");

Tree_init(list);

break;

default:

printf("undefined command\n");

break;

}

}

}


// 굳이 주석이 필요하지 않다. 메뉴 선택지.


tree_pointer CreateNode(int Key) {

tree_pointer newNode;

newNode = (tree_node*)malloc(sizeof(tree_node));


newNode->left = NULL;

newNode->right = NULL;

newNode->key = Key;


return newNode;

}


// 필요없었던 그 함수...





스터디 책 보고 이걸 그냥 c로 짜버리는 사람이 있을까 난 상상도 못하겠다. ㄷㄷ 스터디 끗


반응형

댓글