반응형
728x90
반응형
 

C Data Structure - 이진 탐색 트리 1

오늘은 이진 탐색 트리이다. 이전까지는 선형 자료 구조를 공부했었다. 뭐 예를 들어, 일반 배열 리스트, 연결 리스트, 스택, 큐 등의 선형 구조만 포스팅했는데, 드디어! 비 선형 자료 구조이다.

typingdog.tistory.com

 

C Data Structure - 이진 탐색 트리 2

C Data Structure - 이진 탐색 트리 1 오늘은 이진 탐색 트리이다. 이전까지는 선형 자료 구조를 공부했었다. 뭐 예를 들어, 일반 배열 리스트, 연결 리스트, 스택, 큐 등의 선형 구조만 포스팅했는데,

typingdog.tistory.com

 

C Data Structure - 이진 탐색 트리 3

C Data Structure - 이진 탐색 트리 2 C Data Structure - 이진 탐색 트리 1 오늘은 이진 탐색 트리이다. 이전까지는 선형 자료 구조를 공부했었다. 뭐 예를 들어, 일반 배열 리스트, 연결 리스트, 스택, 큐 등

typingdog.tistory.com

자 드디어 마지막이다. 지긋지긋하다 정말 ㅋ

코드와 실행결과이다.

 

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
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
#include <stdio.h>
#include <stdlib.h>
 
 
#define TRUE 1
#define FALSE 0
#define STACK_LEN 20
#define QUEUE_LEN 20
 
struct binary_tree_node
{
    int data;
    struct binary_tree_node* left;
    struct binary_tree_node* right;
};
typedef struct binary_tree_node tree;
 
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 스택으로 메모리 관리.
struct array_stack
{
    struct binary_tree_node* stack_array[STACK_LEN];
    int top_index;
};
typedef array_stack stack;
 
// 배열 기반 원형 큐 -> 레벨 순회를 위해서;
struct array_queue
{
    int front;
    int rear;
    struct binary_tree_node* queue_array[QUEUE_LEN];
    int count;
};
typedef struct array_queue queue;
 
/* 스택 함수 모음 */
void StackInit(stack* s);
int SIsEmpty(stack* s);
int SIsFull(stack* s); // 증가시키고 대입이기 때문에 현 index에서 증가시키고 대입이 가능한지를 확인해야함.
void SPush(stack* s, struct node* data); // 증가시키고 대입.
struct binary_tree_node* SPop(stack* s);
struct binary_tree_node* SPeek(stack* s);
void ShowStack(stack* s);
 
struct binary_tree_node* CreateNodeAuto(stack* s);
void RemoveAllNodeAuto(stack* s);
 
/* 큐 함수 모음 */
void QueueInit(queue* q);
int QIsEmpty(queue* s);
tree* Dequeue(queue* q);
tree* Enqueue(queue* q, tree* data);
void ShowQueue(queue* q);
 
 
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
// 트리 관련 함수
// 생성 및 삽입
tree* AutoCreateBinaryTreeNode(tree* r, stack* s, int data)
{
    // 조회를 위한 포인터 생성
    tree* index = r;
    // 새로운 노드 생성
    tree* newnode = CreateNodeAuto(s);
    // 새로운 노드 초기화
    newnode->data = data;
    newnode->left = NULL;
    newnode->right = NULL;
 
    // 순회 index가 NULL이 될 때까지 계속 돌린다.
    if (index == NULL)
        return newnode;
    else
    {
        while (index != NULL// TRUE
        {
            if (data < index->data)
            {
                if (index->left == NULL)
                {
                    index->left = newnode; // 니가 있어야 할 곳은 여기야.
                    break;
                }
                // 현재 노드의 자식 중 left node로 간다.
                else
                {
                    index = index->left;
                }
            }
            else if (data > index->data)
            {
                if (index->right == NULL)
                {
                    index->right = newnode; // 니가 있어야 할 곳은 여기야.
                    break;
                }
                else
                {
                    index = index->right;
                }
            }
        }
        return r;
    }
}
// 전체 순회(전위, 중위, 후위)
void ShowPreOrderPath(tree* sub_root)
{
    if (sub_root != NULL)
    {
        printf("[%d] - ", sub_root->data);
        ShowPreOrderPath(sub_root->left);
        ShowPreOrderPath(sub_root->right);
    }
    return;
}
void ShowInOrderPath(tree* sub_root)
{
    if (sub_root != NULL)
    {
        ShowInOrderPath(sub_root->left);
        printf("[%d] - ", sub_root->data);
        ShowInOrderPath(sub_root->right);
    }
    return;
}
void ShowPostOrderPath(tree* sub_root)
{
    if (sub_root != NULL)
    {
        ShowPostOrderPath(sub_root->left);
        ShowPostOrderPath(sub_root->right);
        printf("[%d] - ", sub_root->data);
    }
    return;
}
void ShowLevelOrderPath(tree* sub_root, queue* q)
{
    tree* node = NULL;
    Enqueue(q, sub_root);
    while (!QIsEmpty(q))
    {
        node = Dequeue(q);
        printf("[%d] - ", node->data);
 
        if (node->left != NULL)
            Enqueue(q, node->left);
        if (node->right != NULL)
            Enqueue(q, node->right);
    }
    printf("QIsEmpty(q) : %d\n", QIsEmpty(q));
    return;
}
// 탐색
tree* SearchNode(tree* sub_root, int search_data)
{
    if (sub_root == NULL)
        return NULL;
    else if (search_data == sub_root->data)
        return sub_root;
    else if (search_data < sub_root->data)
        return SearchNode(sub_root->left, search_data);
    else if (search_data >= sub_root->data)
        return SearchNode(sub_root->right, search_data);
}
 
// 삭제
void RemoveNode(tree** root, int search_data)
{
    tree* target = NULL* target_parent = NULL;
    tree* succ = NULL* succ_parent = NULL;
 
    // 타겟 탐색
    target = *root;
    while (target != NULL)
    {
        if (target->data == search_data)
        {
            break;
        }
        else if (search_data < target->data)
        {
            target_parent = target;
            target = target->left;
        }
        else if (search_data > target->data)
        {
            target_parent = target;
            target = target->right;
        }
    }
 
    // 타겟의 유형 파악
    if (target->left == NULL && target->right == NULL// 타겟이 leaf node인 경우
    {
        if (target_parent == NULL && target == *root) // 노드가 루트 하나만 달랑 있는 경우
            *root = NULL;
        else if (target == target_parent->left)
            target_parent->left = NULL;
        else if (target == target_parent->right)
            target_parent->right = NULL;
    }
    else if (target->left == NULL || target->right == NULL)
    {
        if (target_parent == NULL && target == *root)
            *root = (target->left != NULL) ? target->left : target->right;
        else if (target == target_parent->left)
            target_parent->left = (target->left != NULL) ? target->left : target->right;
        else if (target == target_parent->right)
            target_parent->right = (target->left != NULL) ? target->left : target->right;
 
    }
    else if (target->left != NULL && target->right != NULL)
    {
        succ = target->right, succ_parent = target;
        while (succ->left != NULL)
        {
            succ_parent = succ;
            succ = succ->left;
        }
        target->data = succ->data;
 
        if (succ_parent->left == succ)
            succ_parent->left = succ->right;
        else if (succ_parent->right == succ)
            succ_parent->right = succ->right;
    }
    return;
}
 
 
int main(void)
{
    tree* root = NULL;
    tree* target = NULL;
    stack s;
    queue q;
 
    StackInit(&s);
    QueueInit(&q);
 
    //삽입
    /*root = AutoCreateBinaryTreeNode(root, &s, 15);
    root = AutoCreateBinaryTreeNode(root, &s, 7);
    root = AutoCreateBinaryTreeNode(root, &s, 20);
    root = AutoCreateBinaryTreeNode(root, &s, 3);
    root = AutoCreateBinaryTreeNode(root, &s, 10);
    root = AutoCreateBinaryTreeNode(root, &s, 17);
    root = AutoCreateBinaryTreeNode(root, &s, 27);
    root = AutoCreateBinaryTreeNode(root, &s, 1);
    root = AutoCreateBinaryTreeNode(root, &s, 2);
    root = AutoCreateBinaryTreeNode(root, &s, 9);
    root = AutoCreateBinaryTreeNode(root, &s, 13);*/
 
    root = AutoCreateBinaryTreeNode(root, &s, 10);
    root = AutoCreateBinaryTreeNode(root, &s, 4);
    root = AutoCreateBinaryTreeNode(root, &s, 12);
    root = AutoCreateBinaryTreeNode(root, &s, 2);
    root = AutoCreateBinaryTreeNode(root, &s, 5);
    root = AutoCreateBinaryTreeNode(root, &s, 20);
 
 
    // 전위 순회
    ShowPreOrderPath(root);
    fputc('\n', stdout);
 
    // 중위 순회
    ShowInOrderPath(root);
    fputc('\n', stdout);
 
    // 후위 순회
    ShowPostOrderPath(root);
    fputc('\n', stdout);
 
    // 레벨 순회
    ShowLevelOrderPath(root, &q);
    fputc('\n', stdout);
 
    // 4와 12를 각각 탐색
    target = SearchNode(root, 4);
    if (target != NULL)
        printf("%p 주소에서 %d 값을 찾았습니다.\n", target, target->data);
    else
        printf("해당 값이 트리에 존재하지 않습니다\n");
 
    target = SearchNode(root, 12);
    if (target != NULL)
        printf("%p 주소에서 %d 값을 찾았습니다.\n", target, target->data);
    else
        printf("해당 값이 트리에 존재하지 않습니다\n");
 
    // 노드 삭제
    RemoveNode(&root, 10);
 
    // 레벨 순회
    ShowLevelOrderPath(root, &q);
    fputc('\n', stdout);
 
    // 메모리 관리
    RemoveAllNodeAuto(&s);
 
    return 0;
}
 
 
 
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 메모리 관리 스택 관련 함수 모음
 
void StackInit(stack* s)
{
    int i = 0;
    s->top_index = -1;
    for (i = 0; i < STACK_LEN; i++// 순회를 위해서 모두 초기화
        s->stack_array[i] = NULL;
    return;
}
int SIsEmpty(stack* s)
{
    if (s->top_index == -1)
        return TRUE;
    else
        return FALSE;
}
int SIsFull(stack* s) // 증가시키고 대입이기 때문에 현 index에서 증가시키고 대입이 가능한지를 확인해야함.
{
    if ((s->top_index) + 1 < STACK_LEN)
        return FALSE;
    else
        return TRUE;
}
void SPush(stack* s, struct binary_tree_node* data) // 증가시키고 대입임 (top_index가 -1부터 시작하기 때문에)
{
    if (SIsFull(s))
    {
        printf("스택이 가득찼습니다\n");
        return;
    }
    s->stack_array[++(s->top_index)] = data;
    return;
}
struct binary_tree_node* SPop(stack* s)
{
    struct binary_tree_node* rtarget = NULL;
    if (SIsEmpty(s))
    {
        printf("스택이 비어져있습니다\n");
        return NULL;
    }
    rtarget = s->stack_array[s->top_index];
    s->stack_array[s->top_index--= NULL;
    return rtarget;
}
struct binary_tree_node* SPeek(stack* s)
{
    if (SIsEmpty(s))
    {
        printf("스택이 비어져있습니다\n");
        return NULL;
    }
    return s->stack_array[s->top_index];
}
 
void ShowStack(stack* s)
{
    int i = 0;
    printf("{(top:%d)} : ", s->top_index);
    for (i = 0; i <= s->top_index; i++)
        printf("[%p](%d)", s->stack_array[i], s->stack_array[i]->data), putc('-', stdout);
    putc('\n', stdout);
    return;
}
 
binary_tree_node* CreateNodeAuto(stack* s)
{
    struct binary_tree_node* tmp = (struct binary_tree_node*)malloc(sizeof(struct binary_tree_node));
    // 초기화
    tmp->data = 0;
    tmp->left = NULL;
    tmp->right = NULL;
    // 메모리 스택에 추가
    SPush(s, tmp);
 
    return tmp;
}
 
void RemoveAllNodeAuto(stack* s)
{
    binary_tree_node* rtarget = NULL;
    ShowStack(s);
    while (!SIsEmpty(s))
    {
        rtarget = SPop(s);
        free(rtarget);
        ShowStack(s);
    }
    return;
}
 
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 큐 함수 모음
void QueueInit(queue* q)
{
    int i = 0;
 
    q->front = q->rear = 0;
 
    for (i = 0; i < QUEUE_LEN; i++)
        q->queue_array[i] = NULL// 큐 출력을 위한 초기화
 
    q->count = 0;
    return;
}
int QIsEmpty(queue* q)
{
    if (q->front == q->rear)
        return true;
    else
        return false;
}
tree* Dequeue(queue* q)
{
    tree* rval = NULL;
    // 기본 동작 - 비었는가 확인 -> 삭제 -> 인덱스 변경
    if (q->front == q->rear)
    {
        printf("Queue가 이미 비었습니다.\n");
        return 0;
    }
    rval = q->queue_array[q->front];
 
    q->queue_array[q->front= NULL;
    q->front = (q->front + 1) % QUEUE_LEN;
 
    q->count--;
 
    return rval;
}
tree* Enqueue(queue* q, tree* data)
{
    tree* rval = NULL;
    // 기본 동작 - 꽉 찼는가 확인 -> 삽입 -> 인덱스 변경
    // 2.
    if ((q->rear + 1) % QUEUE_LEN == q->front// 가득 찬 경우,
    {
        printf("Queue가 가득 찼습니다.\n");
        return 0;
    }
    // 1.
    q->queue_array[q->rear] = data;
    rval = q->queue_array[q->rear];
    q->rear = (q->rear + 1) % QUEUE_LEN;
 
    q->count++;
 
    return rval;
}
 
void ShowQueue(queue* q)
{
    int i = 0;
    printf("{(r:%2d/f:%2d)} : ", q->rear, q->front);
    for (i = 0; i < q->count; i++)
        printf("[%p]", q->queue_array[i]), putc('-', stdout);
    putc('\n', stdout);
    return;
}
cs

 

728x90
반응형
728x90
반응형
 

C Data Structure - 이진 탐색 트리 2

C Data Structure - 이진 탐색 트리 1 오늘은 이진 탐색 트리이다. 이전까지는 선형 자료 구조를 공부했었다. 뭐 예를 들어, 일반 배열 리스트, 연결 리스트, 스택, 큐 등의 선형 구조만 포스팅했는데,

typingdog.tistory.com

지난 포스팅에서 순회까지 끝냈다. 이제 탐색과 삭제가 남았다.

바로 이어서 탐색을 보자.

이진 탐색 트리의 탐색

이진 탐색 트리에서 탐색과 순회는 다르다. 순회는 모든 노드를 방문하는 것을 이야기하지만, 탐색은 가장 효율적이고 적은 깊이로 노드를 방문하여 원하는 값을 찾는 것인가이다.

1번 : 찾으려고 하는 5 값이 루트 노드의 10 값보다 작으니, 왼쪽 서브 트리를 대상으로 하고 인덱스를 옮긴다.
2번 : 찾으려고 하는 5 값이 노드의 4 값보다 크니, 오른쪽 서브 트리를 대상으로 하고 인덱스를 옮긴다.
그 이후 발견.

위와 같이 서브 트리를 대상으로 옮기고, 또 서브 트리의 서브 트리로 대상을 옮기고 하는 과정에서 봤을 때, 재귀로 해결할 수 있다! 탐색 또한 마찬가지로 순회처럼 재귀를 이용하여 문제를 해결해갈 것이다.

코드를 보면 다음과 같다.

매개 변수인 sub_root에는 서브 트리의 루트 노드가 항상 들어온다. 이 서브 트리의 루트 노드와 값을 비교하여 같으면 탐색 완료, 작으면 다시 왼쪽 서브 트리 재탐색을 위한 재귀 호출, 크면 다시 오른쪽 서브 트리 재탐색을 위한 재귀 호출 순서로 조건 및 연산 수행을 진행하면 된다.

이진 탐색 트리의 삭제

아.. 삭제는 ㅋㅋ 여러 유형으로 나뉜다 쉽지가 않다. 왜냐하면? 자식이 없는 단말 노드를 삭제할 경우, 그냥 해당 단말 노드만 제거하면 끝나는 문제이지만 자식 노드가 존재하는 노드를 삭제할 경우 문제가 커진다. 

일단은 그림 형태로 유형 별로 확인한 뒤 코드를 보자.

삭제할 타겟이 단말(리프) 노드인 경우

간단하다. 그냥 끝에 있는 노드이므로 바로 삭제를 하면 되는데, 다만! 삭제할 타겟의 부모의 링크(left or right)가 NULL을 가리키도록 설정해주면 된다. 다음의 그림과 같이 말이다.

위에 해당하는 코드는 다음과 같다.

삭제할 타겟의 자식 노드가 하나만 존재하는 경우

이 경우 또한 간단하다. 아래의 그림과 같은 경우인데 삭제할 타겟의 부모의 링크와 삭제할 타겟이 가지고 있는 하나의 자식 노드를 연결해주면 된다.

위에 해당하는 코드는 다음과 같다.

삭제할 타겟의 자식 노드가 두 개 모두 존재하는 경우

이 경우는 좀 답이 없다. 물론 설명하기 답이 없다는 것이다. ㅋㅋ 좀만 생각해보면 쉽다! 

먼저, 이전의 노드 삭제 유형들처럼 타겟을 삭제하고 부모와 자식을 연결해주는 등의 간단한 방법으로 끝날 문제가 아니다.

삭제할 타겟 노드가 자식 노드를 두 마리나(?) 가지고 있는 바람에 왼쪽 자식 노드의 서브 트리와 오른쪽 자식 노드의 서브 트리를 만족할 수 있는 값으로 대체 노드 선별해야한다. 

아 그러면? 삭제할 타겟 노드의 자식들 중 하나로 선택하면 되는 것 아니냐?

응 안된다. 왜냐하면 그림에 자세히 설명을 해 놓았다. 아래의 그림에서 두 번째 케이스에 해당하는 경우다. 혼돈하지 말아야 한다.

그러면 자식 중에 아무나 하나를 그냥 올리면 안되면 어떻게 올려야 하나?

왼쪽 서브 트리에서 가장 큰 값을 갖는 노드를 대체 노드로 선별하거나,
오른쪽 서브 트리에서 가장 작은 값을 갖는 노드를 대체 노드로 선별해야한다.

두 경우 중 하나를 선택해야 한다. 그 선택은 뭐 개발자 마음이고, 코드 작성자 마음이다. 선택했다면 다음과 같은 순서를 떠올릴 수 있다.

1. 대체 노드의 링크들을 타겟 노드의 링크로 전부 동기화 한다.
2. 대체 노드의 부모의 링크를 적절한 포인터(노드 혹은 NULL)로 변경한다.
3. 타겟 노드를 제거한다.

이러한 순서를 떠올렸는데 여기서 약간의 꼼수(?)를 쓴다고 하며, 지혜를 발휘한다고 읽는다. 뭐냐하면, 대체 노드를 굳이 드러내어 타겟이 있던 자리로 옮겨서 링크를 수정할게 아니라, 타겟 노드의 값만 대체 노드의 값으로 바꿔준다면 링크들을 굳이 변경할 필요가 없기 때문이다. 

1번 연산이 굉장히 부담 그 자체이다. 헤깔려죽겠는데.. 2번과 3번은 어차피 대체 노드에 대해서 해야할 작업이기 때문에 그렇다 치더라도, 1번 연산은 안 해도 될 일을 하는 것과 다름이 없다! ㅋㅋ

그래서 뭐 어찌 되었든 간에 왼쪽/오른쪽 중에 선택을 했다면, 다음 그림으로 그 선택 이후의 과정을 설명한다. 번호 순서대로 색깔과 연관 지어서 차근 차근 읽으면 된다.

 

이에 따른 코드는 아래와 같으며, 타겟 노드를 루트 노드로 한 서브 트리의 오른쪽 자식의 서브 트리 군에서 가장 작은 값을 선택하여 타겟 노드의 자리에 대체하기로 하였다.

하.. 이로써 삭제까지 끝이다.

일요일에 정말 집중도 안되고, 포스팅을 하면 할수록 코드 작성 때는 떠 올리지도 않았던 세세한 부분까지 의구심이 들어서 시간이 두배, 세배 드는 것 같다.

뭐 다음 시간에는 코드와 실행 결과만 올린다.

728x90
반응형

+ Recent posts