반응형
728x90
반응형

진짜 다 까먹어서 다시 예제 쳐보고 다시 올린다... 정말 미리미리 올리고 자주자주 봐야합니다..

포스팅 순서가 사실 멀티 프로세싱이 먼저이지만, 게임 서버 개발에 사용한 것은 멀티 플렉싱 모델이었기 때문에 먼저 올렸었는데, 사실 멀티 프로세싱이 무엇인지, 그리고 그 단점이나 필요한 영역은 어느 부분인지 알아야 한다.

그래서 포스팅을 또 하게 되었다.

내가 구현했던 서버의 모델은 멀티 플렉싱 모델이었다(select, epoll) 이 모델은 위와 같은 구조로 이루어진다.

여러 학생들이 클라이언트들이고, 교사하나프로세스이다.
질의와 답변을 필요로 하는 학생들, 즉 송수신을 요청하는 애들만 빠른 반복문 처리를 통해 처리한다.

이러한 모델에서는 여러 명의 클라이언트들이 송수신이나 연결 등의 여러 요청을 할 때마다 그 요청만큼만 처리하는 구조이기 때문에 대용량의 파일을 송수신하는 연결과는 적합하지 않다. 

반면, 멀티 프로세싱 모델은 다음과 같다.

교사들 프로세스들이고, 여러 학생들이 클라이언트들인것이다.
학생들이 질의와 답변을 필요로 하든 안 하든 무조건 프로세스와 연결되어 1대1 통신이 진행되는 것이다.

이런 구조에서는 시간이 오래 걸리는 상담이나 컨설팅 같은 서비스가 교사와 학생들 사이에서 제공되어야 할 법하다. 마찬가지로 프로세스-클라이언트 입장에서 봤을 때, 용량이 크고 시간이 오래 걸리는 파일 전송 등이 자원 효율이 클 것이다.

이러한 멀티 프로세싱 기반의 소켓을 작성하기 위해서는 fork() 에 대한 개념이 필요하다.

그전에! 프로세스란 무엇인가?

운영체제 관점에서 별도의 실행 흐름을 구성하는 단위. 
생성될 때, PCB(Process Controll Block) 라고 하는 프로세스에 대한 정보 구조가 생성되며,
이 정보에는 해당 프로세스에 대한 구체적인 정보들이 저장된다
(메모리 할당, 파일 디스크립터 테이블 등등 수 많은..)

프로세스의 복사본을 생성하는 fork()

위와 같은 기본 코드가 있다. 10번 줄을 보면 fork 라는 함수를 호출한다. 이 fork() 함수프로세스를 그대로 복사하는 함수이다. 이 함수가 호출되는 순간!! 프로세스가 그대~로 복사되어 10번 라인부터는 흐름이 각각 따로 진행된다. 다음과 같이 말이다.

그렇다면 이 함수가 반환하는 값은 무엇일까? 원래 실행되던 흐름부모 프로세스라고 하고, 복사된 실행 흐름자식 프로세스라고 한다.

부모 프로세스의 실행 흐름일 때, 10번 라인에서 반환되는 값은 자식 프로세스의 프로세스 ID 번호이다.
자식 프로세스의 실행 흐름일 때, 10번 라인에서 반환되는 값은 무조건 0이다.

그래서 12번 라인과 16번 라인에서는 부모 프로세스와 자식 프로세스의 실행 흐름을 구별하기 위해 pid를 0인지 아닌지를 비교하고 각 실행 흐름에 맞는 처리를 진행하면 된다.

 

이런 식으로 프로세스를 생성하므로, 클라이언트가 접속하면 프로세스를 복사하여 클라이언트에게 할당해줘서 송수신처리를 담당하면 된다. 

그런데.. 이러한 방식에는 큰 문제가 있었으니..

 

이번 포스팅에서 제일 중요한 것은 프로세스의 복사는 아예 메모리까지 통채로 복사되어
실행 흐름이 완전 다른 별개로 나뉘어진 부모 프로세스, 자식 프로세스라는 것이다!

728x90
반응형
728x90
반응형

드디어 그래프에 대한 포스팅이다. 여러가지 병행하며 정리할 것도 너무 많아서 ㅋㅋ 미루고 미루다 이제 올리게 된다. 오늘은 그래프의 기본 중에 기본인 용어 및 정의 정리이다.

그래프란?

먼저, 그래프란 아래의 설명과 같다.

비선형 자료구조, 즉, 트리 또한 그래프의 일종이라는 것을 알 수 있다!

이러한 그래프에는 간선의 방향성이라든가, 가중치, 부분 집합에 따라서 그래프의 종류가 나뉜다 이를 하나씩 나열해 볼 것이다.

무방향 / 방향 그래프

간선에 방향이 없고, 있고의 차이이다.

완전 그래프

정점 별로 모든 정점과 연결되어 있는 그래프를 완전 그래프라고 하는데, 이 때, 방향 완전 그래프는 무방향 완전 그래프보다 간선의 수가 2배 많다.

가중치 그래프와 부분 그래프

가중치 그래프는 간선에 가중치를 두고, 예를 들어, A에서 C로 가려면 3+1 혹은 4+5 비용이 든다고 표현할 수 있다. 부분 그래프는 말 그대로 그래프의 부분만을 그려낸 것이다.

그래프의 집합 표시

이러한 그래프를 집합 형태로 표현을 할 수 있다

그래프를 코드로 표현하기 위한 약속

그래프는 진짜 들쑥 날쑥인데 어떻게 표현하지 싶었는데, 사람들은 참 머리가 좋은 것 같다.

그래프가 어떻게 생겼든, 크기가 어떻든 정점 사이가 얼마나 멀든 좁든 간에 각 정점과 그 사이의 간선만 표현할 수 있다면, (예를 들어 어느 점과 어느 점이 어떤 선으로 연결되어 있는지 등의 정보) 오케이이다.

그 정점과 간선 등의 관계를 표현하는 방법은 위 그림에서처럼 인접 행렬, 인접 리스트가 그 방법이다. 방법은 그림으로 보는 것이 자세하고 편해서 그림으로 표현을 해 보았다.

728x90
반응형
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 - 이진 탐색 트리 1

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

typingdog.tistory.com

여기에 이어서 이진 탐색 트리에 대해서 분석하고 기록할 것이다.

준비 및 구성

먼저, 이전 포스팅에서의 개념과 조건들을 토대로 어떻게 준비할 것인가이다!

트리 구성의 준비물이 되는 노드이다! 노드의 구성은 다음과 같다.

크게 설명할 것이 없다. 이 노드 구조체를 통해서 값과 이진 탐색 트리 조건에 맞는 자식 노드를 가리킬 포인터 2개의 구성이 끝인 것이다. 

이런 구조체로 노드를 생성할 때에는 꼭 다음과 같은 상태로 초기화되는 것이 보장되어야 한다.

 

트리에 접근하는 방법(루트 노드에 접근하는 방법)

삽입 연산을 하기 이전에 트리에 접근할 때에는 무조건 루트 노드부터 접근을 시작해야한다. 그 루트 노드로의 접근을 어떤 식으로 할 수 있는지, 그 루트 노드를 어떻게 정하는지를 기록한다.

위 코드는 메인 함수 내에서 실행되는 코드로 root 라고 하는 포인터로 트리의 루트 노드를 가리키도록 설계해놓았다.

트리에 노드 삽입 연산

먼저, 삽입 연산은 그림으로 나타냈을 때, 아래와 같다.

위 삽입 연산에 해당되는 코드를 함께 보는게 좋을 것 같다.

먼저, 트리에 노드가 0개 일 때 의 노드 생성이다.

트리에 노드가 1개 이상일 경우에는 다음처럼 삽입 연산을 수행한다.

트리 순회 연산

트리에 노드를 추가만 해서는 뭘 하는가! 순회도하고 탐색도 할 수 있어야, 탐색 트리이지. 먼저, 순회 연산에 대해 알아본다.

하.. 일단(ㅋ) 순회 연산은.. 여러 종류가 있다. 이 여러 종류의 연산 과정을 내 블로그 내에서 최초 도입한 신개념 비디오 장치를 통해 촬영된 것을 올릴 것이다.

일단 트리에서의 순회는 조금 특이하다. 

첫 노드인 루트 노드를 기준으로 한 큰 트리가 존재하지만, 첫 노드의 자식을 기준으로 또 하나의 서브 트리가 존재한다. 이 서브 트리를 하나의 대상으로 연산이 이루어지고, 또 그 자식 노드를 기준으로 또 서브 트리가 존재하며 단말 노드가 될 때까지 서브 트리 군이 형성된다. -> 재귀적으로 문제를 풀 수 있다는 것이다.

그래서 이 순회에서는 재귀를 통해 순회를 진행할 것이다. 삭제 또한 마찬가지이나 다양한 방법들을 넣기 위해서 순회에만 재귀를 적용하도록하고, 삭제에서는 반복문을 통해 삭제를 진행한다.

전위 순회 : 루트 노드를 기준으로 먼저 루트 노드의 값을 읽고, 그 다음 순차적으로 왼쪽 트리를 방문하고, 그 뒤에 오른쪽 트리를 방문하는 방법이다.

서브 트리의 루트 방문(값 Get) -> 왼쪽 링크를 통한 왼쪽 서브 트리 방문 -> 오른쪽 링크를 통한 오른쪽 서브 트리 방문

전위 순회 코드

 

중위 순회 : 루트 노드를 기준으로 먼저 왼쪽 트리를 방문하고, 그 다음 해당 루트 노드의 값을 읽고, 그 다음 오른쪽 트리를 방문하는 방법이다.

왼쪽 링크를 통한 왼쪽 서브 트리 방문 -> 서브 트리의 루트 방문(값 Get) -> 오른쪽 링크를 통한 오른쪽 서브 트리 방문

중위 순회 코드

후위 순회 : 루트 노드를 기준으로 먼저 왼쪽 트리를 방문하고, 그 다음 오른쪽 트리를 방문한 뒤에 해당 루트 노드의 값을 읽는 순회이다.

왼쪽 링크를 통한 왼쪽 서브 트리 방문 -> 오른쪽 링크를 통한 오른쪽 서브 트리 방문 -> 서브 트리의 루트 방문(값 Get)

후위 순회 코드

레벨 순회 : 레벨 순회는 레벨 1부터 트리의 말단 리프 노드들이 있는 레벨까지 큐 자료 구조에 순서대로 넣고 출력하여 순회하는 간단한 순회 법이며, 레벨 순서 & 왼 -> 오 순으로 순회가 이루어진다.

레벨 순회 코드

각 순회의 실제 결과

 

다음 포스팅은 이진 탐색 트리에서 탐색과 삭제 연산을 포스팅할 것이다.

728x90
반응형
728x90
반응형

오늘은 이진 탐색 트리이다. 

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

데이터가 선형으로 연속되지 않는만큼 삽입, 삭제, 탐색 연산에 신경쓸게 더 많다. 그래도 이렇게 선형을 끝내고 비선형을 진행하는게 뿌듯하긴 하지만 이게 들이는 시간에 비해 결과가 그렇게 크지 않아서 비선형 자료구조를 여러 가지의 예제로 다양하게 진행하지는 못할 것 같고, 핵심만 파악할 수 있는 그런 알찬 예제로 기록을 해 둘 것이다.

나중에 다시 봐도 아~ 이게 트리지 할 수 있도록.

일단은 나는 연결 리스트 기반의 이진 탐색 트리를 구현할 것이며, 데이터의 삽입, 삭제, 탐색(레벨 순회, 전위 순회, 중위 순회, 후위 순회)까지 구현할 것이다. 

배열 기반의 이진 트리는 Heap 자료 구조에서 구현이 될 것인데, 아래의 링크에서 Heap 구조를 알 수 있다. 기본은 Heap이지만 내용은 거의 우선 순위 큐이다. 다만, 큐 형태로 Wrapping만 하지 않았을 뿐...

 

C Data Structure - Heap ( Priority Queue, 우선 순위 큐 ) 개념 및 코드 구현

공부를 하고 정리하는 것이 매우 중요하다. 근데 그 정리를 하기가 조금 귀찮은 것이 아니다. 앞으로는 그 때 그 때 정리하도록 하고, 모아두지 말아야겠다.. 업로드 할 것이 한 두 가지가 아니다

typingdog.tistory.com

특이사항이라면,

이전과 마찬가지로 메모리 관리는 기존에 공부했던 배열 기반 스택을 통해 이루어진다. 
레벨 순회를 위해서 배열 기반 원형 큐를 이용한다.

트리를 제외하고 부가적으로 자료구조를 이용할 때에는 메모리를 할당하지 않는 배열 기반으로 

그렇다면 군말없이 바로 시작해보자!

트리란? 

먼저 트리의 구조나 용어를 설명한다. 트리는 이러하다.

트리는 다음과 같이 여러 형태의 트리들이 올 수 있지만

그 중에 이진 탐색 트리에 대해서 기록할 것이다. 위 그림에서 가운데에 있는 트리 구조가 가장 이진 탐색 트리를 잘 나타낸 이진 탐색 트리의 모형이라고 보면 된다.

그러면 이진 트리란?

이진 트리에 논하기에 앞서, 가장 작은 단위인 노드부터 자세히 살펴보면 다음과 같은 구조로 되어 있다.

이런 노드를 요리조리 이용하면 트리를 완성시킬 수 있는데, 노드가 이렇게 최대 두 개의 자식을 가질 수 있는 형태로 이루어져 있으며, 이러한 노드들로 구성되어 있는 트리를 이진 트리라고 한다.

그러면 이진 탐색 트리는 또 무엇인가? 이진 탐색 트리는 이진 트리이긴 한데, 탐색이 가능한 트리라서 이진 탐색 트리라고 한다! 그러기 위해선 일정 조건들이 추가 되어야 하는데 조건은 다음과 같다.

위와 같은 조건을 가지고 있는 이진 트리이진 탐색 트리가 될 수 있다.

다음 포스팅은 삽입, 삭제, 탐색을 적절히 나눠서 여러 차례 포스팅한다.

728x90
반응형
728x90
반응형

오늘은 덱? 디큐? 라고 불리우는 자료구조이다.

Dequeue 는 매우 혼종이다. 큐라고는 불리우는데 큐의 특징 이외에 스택의 특징을 가지고 있기 때문이다. 선입선출 / 후입선출 등의 개념을 모두 가지고 있다. 먼저 들어온 놈이 먼저 나갈 수 있고, 나중에 들어온 놈이 먼저 나갈 수도 있다는 것이다.

도데체 어떤 구조이길래 위와 같은 특징을 가지고 있을까? 다음과 같다.

위와 같은 구조이다. 뒤에서도 (tail의 사이드) 추가 및 삭제가 가능해야 하기 때문에 꼭 양방향 링크로 구성이 되어야 한다!!!!! 

그런데 뭐, 이제까지 다 다뤄봤던 구조들이기 때문에 예를 들어, 양방향 링크라든지 head와 tail이 함께 있는 구조라든지 어렵지 않은 부분들이므로 간단하게 설명한다.

초기화와 Dequeue의 비워짐 / 꽉 참의 확인

먼저, 초기화는 Head와 Tail 을 NULL로 초기화 하는 것으로 시작한다.

그리고 이러한 초기화에 따라서, Head와 Tail이 NULL 인 경우가 비워진 경우이고, 해당 Dequeue의 꽉 찬 상태는 확인할 필요가 없다. 왜냐하면 연결 기반의 큐이기 때문에 추가하는 수의 제한이 없다고 보면 되기 때문이다.

노드 추가

위 그림으로 설명이 충분하다고 생각한다. 노란색이 추가되는 노드이고, head와 tail 은 각각 옮겨가는 것을 표현한 것이다.

노드 삭제

노드 그림을 그리지 않고 코드로만 설명해도 되겠다고 판단하여 그림을 그리지 않는다. 코드의 주석만으로 충분한 설명이 되었다고 본다.

탐색

탐색은 다른 리스트들과 마찬가지로 링크들을 타면서 순회하는 것이기 때문에 딱히 구현하지 않았다.

메모리 관리

배열 기반의 원형 큐를 통해 메모리 관리를 한다.

 

소스 코드 및 실행 결과

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
#include <stdio.h>
#include <stdlib.h>
 
#define QUEUE_LEN 11
#define TRUE 1
#define FALSE 0
 
struct node
{
    int data;
    struct node* prev;
    struct node* next;
};
 
struct list_dequeue
{
    struct node* head;
    struct node* tail;
    int count;
};
typedef struct list_dequeue dequeue;
 
struct array_queue // 배열 기반 원형 큐 구현
{
    int front;
    int rear;
    int count;
    struct node* queue_array[QUEUE_LEN];
};
typedef struct array_queue queue;
 
void QueueInit(queue* q);
int QisEmpty(queue* q);
struct node* Dequeue(queue* q);
void Enqueue(queue* q, struct node* data);
void ShowQueue(queue* q);
void RemoveAllNodeAuto(queue* q);
struct node* CreateNodeAuto(dequeue* dq, queue* q);
 
 
void DequeueInit(dequeue* dq)
{
    // head와 tail을 NULL로 초기화
    dq->head = NULL;
    dq->tail = NULL;
    // count 값을 0으로 초기화
    dq->count = 0;
    return;
}
 
int DQIsEmpty(dequeue* dq)
{
    if (dq->head == NULL && dq->tail == NULL// head와 tail이 동일하게 NULL을 가리킨다면 true 반환
        return TRUE;
    else // 아니면 false 반환
        return FALSE;
}
 
void DQAddFirst(dequeue* dq, queue* q, int data)
{
    // 새 노드 생성 ->  prev와 next는 모두 NULL
    struct node* newnode = CreateNodeAuto(dq, q);
    newnode->data = data;
 
    if (DQIsEmpty(dq)) // 비어있다면 head와 tail에게 새 노드 주소를 던짐
    {
        dq->head = dq->tail = newnode;
    }
    else // 안 비어있다면 next는 원래 head가 가리키고 있던 주소, head만 변경.
    {
        newnode->next = dq->head;
        dq->head->prev = newnode;
        dq->head = newnode;
    }
    // count 증가
    dq->count++;
 
    return;
}
void DQAddLast(dequeue* dq, queue* q, int data)
{
    // 새 노드 생성 ->  prev와 next는 모두 NULL
    struct node* newnode = CreateNodeAuto(dq, q);
    newnode->data = data;
 
    if (DQIsEmpty(dq)) // 비어있다면 head와 tail에게 새 노드 주소를 던짐
    {
        dq->head = dq->tail = newnode;
    }
    else // 안 비어있다면 prev는 원래 tail이 가리키고 있던 주소, tail만 변경.
    {
        newnode->prev = dq->tail;
        dq->tail->next = newnode;
        dq->tail = newnode;
    }
    // count 증가
    dq->count++;
    return;
}
 
int DQRemoveFirst(dequeue* dq)
{
    struct node* target = NULL;
    int data = 0;
    if (DQIsEmpty(dq)) // 비어있다면 x
    {
        printf("dequeue가 이미 비어져 있습니다.\n");
        return -1;
    }
    // 비어있지 않으면 현재 head를 임시 변수에 저장
    target = dq->head;
    // head를 현재 head의 next로 옮긴다
    dq->head = dq->head->next;
    // 임시 변수에서 값 추출
    data = target->data;
    // 카운트를 감소한다.
    dq->count--;
    // 변경된 헤드가 NULL인지 확인한다.
    if (dq->head == NULL)
    {
        dq->tail = NULL;
    }
    else
    {
        dq->head->prev = NULL;
    }
    // 메모리 해체는 원형 큐에서.
    return data;
}
int DQRemoveLast(dequeue* dq)
{
    struct node* target = NULL;
    int data = 0;
    if (DQIsEmpty(dq)) // 비어있다면 x
    {
        printf("dequeue가 이미 비어져 있습니다.\n");
        return -1;
    }
    // 비어있지 않으면 현재 tail을 임시 변수에 저장
    target = dq->tail;
    // tail을 현재 tail의 prev로 옮긴다
    dq->tail = dq->tail->prev;
    // 임시 변수에서 값 추출
    data = target->data;
    // 카운트를 감소한다.
    dq->count--;
    // 변경된 헤드가 NULL인지 확인한다.
    if (dq->tail == NULL)
    {
        dq->head = NULL;
    }
    else
    {
        dq->tail->next = NULL;
    }
    // 메모리 해체는 원형 큐에서.
    return data;
}
 
int DQGetFirst(dequeue* dq)
{
    // 비어있다면 x
    if (DQIsEmpty(dq))
    {
        printf("dequeue가 이미 비어져 있습니다.\n");
        return -1;
    }
    // 비어있지 않으면 현재 head의 값을 리턴
    return dq->head->data;
}
int DQGetLast(dequeue* dq)
{
    // 비어있다면 x
    if (DQIsEmpty(dq))
    {
        printf("dequeue가 이미 비어져 있습니다.\n");
        return -1;
    }
    // 비어있지 않으면 현재 tail의 값을 리턴
    return dq->tail->data;
}
 
void ShowDequeue(dequeue* dq)
{
    struct node* dq_index = NULL;
    for (dq_index = dq->head; dq_index != NULL; dq_index = dq_index->next)
        printf("[%p/%d] - ", dq_index, dq_index->data);
    fputc('\n', stdout);
    return;
}
 
 
 
int main(void)
{
    queue q;
    dequeue dq;
 
    QueueInit(&q);
    DequeueInit(&dq);
 
    DQAddFirst(&dq, &q, 2); ShowDequeue(&dq);  // ShowQueue(&q); 
    DQAddFirst(&dq, &q, 1); ShowDequeue(&dq);  // ShowQueue(&q);
    DQAddFirst(&dq, &q, 3); ShowDequeue(&dq);  // ShowQueue(&q);
    DQAddLast(&dq, &q, 4); ShowDequeue(&dq);  //ShowQueue(&q);
    DQAddLast(&dq, &q, 5); ShowDequeue(&dq);  //ShowQueue(&q);
    DQAddLast(&dq, &q, 6); ShowDequeue(&dq);  //ShowQueue(&q);
 
    printf("----- 1\n");
    while (!DQIsEmpty(&dq))
        printf("%d ", DQRemoveFirst(&dq));
    fputc('\n', stdout);
    RemoveAllNodeAuto(&q);
 
    DQAddFirst(&dq, &q, 3);
    DQAddFirst(&dq, &q, 2);
    DQAddFirst(&dq, &q, 1);
 
    DQAddLast(&dq, &q, 4);
    DQAddLast(&dq, &q, 5);
    DQAddLast(&dq, &q, 6);
 
    printf("----- 2\n");
    while (!DQIsEmpty(&dq))
        printf("%d ", DQRemoveLast(&dq));
    fputc('\n', stdout);
 
    RemoveAllNodeAuto(&q);
 
    // 6 5 3 1 2 4 7 8 
    DQAddFirst(&dq, &q, 1);
    DQAddLast(&dq, &q, 2);
    DQAddFirst(&dq, &q, 3);
    DQAddLast(&dq, &q, 4);
    DQAddFirst(&dq, &q, 5);
    DQAddFirst(&dq, &q, 6);
    DQAddLast(&dq, &q, 7);
    DQAddLast(&dq, &q, 8);
 
    printf("----- 3\n");
    while (!DQIsEmpty(&dq))
        printf("%d ", DQRemoveFirst(&dq));
    fputc('\n', stdout);
 
    RemoveAllNodeAuto(&q);
 
 
 
    return 0;
}
 
///// MEMORY QUEUE
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;
}
struct node* Dequeue(queue* q)
{
    struct node* target;
    // 기본 동작 - 비었는가 확인 -> 삭제 -> 인덱스 변경
    if (q->front == q->rear)
    {
        printf("Queue가 이미 비었습니다.\n");
        return NULL;
    }
    target = q->queue_array[q->front];
    q->queue_array[q->front= NULL;
    q->front = (q->front + 1) % QUEUE_LEN;
    return target;
}
void Enqueue(queue* q, struct node* data)
{
    // 기본 동작 - 꽉 찼는가 확인 -> 삽입 -> 인덱스 변경
    if ((q->rear + 1) % QUEUE_LEN == q->front// 가득 찬 경우,
    {
        printf("Queue가 가득 찼습니다.\n");
        return;
    }
    q->queue_array[q->rear] = data;
    q->rear = (q->rear + 1) % QUEUE_LEN;
    q->count++;
    return;
}
 
void ShowQueue(queue* q)
{
    int i = 0;
    printf("{(r:%2d/f:%2d)} : ", q->rear, q->front);
    for (i = 0; i < QUEUE_LEN; i++)
        printf("[%2p]", q->queue_array[i]), putc('-', stdout);
    putc('\n', stdout);
    return;
}
 
// 내가 정의하는 함수
struct node* CreateNodeAuto(dequeue* dq, queue* q)
{
    struct node* tmp = (struct node*)malloc(sizeof(struct node));
    // 초기화
    tmp->data = 0;
    tmp->prev = NULL;
    tmp->next = NULL// tmp->next = s->head;
    // 큐 추가
    Enqueue(q, tmp);
 
    return tmp;
}
 
void RemoveAllNodeAuto(queue* q)
{
    struct node* rtarget = NULL;
    ShowQueue(q);
    while (!QisEmpty(q))
    {
        rtarget = Dequeue(q);
        printf("[%p/(%d)]가 제거되었습니다.\n", rtarget, q->count);
        ShowQueue(q);
        free(rtarget);
        q->count--;
    }
    return;
}
cs

 

728x90
반응형
728x90
반응형

리스트 기반 큐는 뭐 간단하다. 그냥 일반 큐인데 연결 리스트로 이루어진 큐이다.

기본 형태는 다음과 같다.

매우 간단하다. 그래서 행복하다 

기본적으로 배열 기반의 큐와 똑같이 동작한다. 그러나 배열 기반 큐와 다른 점은 크기가 정해져 있지 않고 동적이고 가변이기 때문에 뭐, 꽉 찬 것으로 보이지만 비어있네, 마네 뭐 이런 배열 기반 큐의 치명적인 단점을 생각할 필요가 없다.

Front 노드 포인터를 통해서 가장 먼저 들어온 노드를 가리키며, 삭제 연산을 진행할 때마다 Front 노드 포인터를 한 노드씩 옮긴다. 그리고 Rear 노드 포인터를 통해서 가장 마지막에 추가된 노드를 가리키며, 삽입 연산을 진행할 때마다, Rear 노드 포인터를 새로운 노드로 갱신한다. 

삽입 연산

 

삭제 연산

 

꽉참/비어짐 확인 연산

꽉 찼는지는 연결 리스트이므로 확인할 필요가 없으니, 비어져있는 상태만 확인하면 되는데 이 역시 매우 간단하다.

Front  노드  포인터가 NULL인지를 확인하면 된다.

메모리 관리

이번에는 연결 리스트 기반 스택으로 관리한다.

코드 및 실행 결과

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
#include <stdio.h>
#include <stdlib.h>
 
#define STACK_LEN 10
 
#define TRUE 1
#define FALSE 0
 
// 공통 노드 정의
struct node
{
    int data;
    struct node* next;
};
 
// 스택으로 메모리 관리.
struct array_stack
{
    struct node* stack_array[STACK_LEN];
    int top_index;
};
typedef array_stack stack;
 
void StackInit(stack* s);
int SIsEmpty(stack* s);
int SIsFull(stack* s); // 증가시키고 대입이기 때문에 현 index에서 증가시키고 대입이 가능한지를 확인해야함.
void SPush(stack* s, struct node* data); // 증가시키고 대입.
struct node* SPop(stack* s);
struct node* SPeek(stack* s);
void ShowStack(stack* s);
 
struct node* CreateNodeAuto(stack* s);
void RemoveAllNodeAuto(stack* s);
 
// 리스트 기반 큐 자료구조(원형일 필요가 없음)
struct list_queue
{
    struct node* front;
    struct node* rear;
    int count;
};
typedef struct list_queue queue;
 
void QueueInit(queue* q);
int QisEmpty(queue* q);
struct node* Dequeue(queue* q);
void Enqueue(queue* q, stack* s, int data);
void ShowQueue(queue* q);
 
// Main
int main(void)
{
    stack s;
    queue q;
 
    QueueInit(&q);
    StackInit(&s);
 
    printf("큐/스택 생성 시작 --- \n");
 
    Enqueue(&q, &s, 1); ShowQueue(&q);
    Enqueue(&q, &s, 2); ShowQueue(&q);
    Enqueue(&q, &s, 3); ShowQueue(&q);
    Enqueue(&q, &s, 4); ShowQueue(&q);
    Enqueue(&q, &s, 5); ShowQueue(&q);
 
    printf("큐 제거 시작 --- \n");
 
    ShowQueue(&q);
    while (!QisEmpty(&q))
    {
        Dequeue(&q);
        ShowQueue(&q);
    }
    fputc('\n', stdout);
 
    printf("스택 제거 시작 --- \n");
    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 node* data) // 증가시키고 대입임 (top_index가 -1부터 시작하기 때문에)
{
    if (SIsFull(s))
    {
        printf("스택이 가득찼습니다\n");
        return;
    }
    s->stack_array[++(s->top_index)] = data;
    return;
}
struct node* SPop(stack* s)
{
    struct 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 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 < STACK_LEN; i++)
        printf("[%p]", s->stack_array[i]), putc('-', stdout);
    putc('\n', stdout);
    return;
}
 
// 내가 정의하는 함수
 
struct node* CreateNodeAuto(stack* s)
{
    struct node* tmp = (struct node*)malloc(sizeof(struct node));
    // 초기화
    tmp->data = 0;
    tmp->next = NULL// tmp->next = s->head;
    // 메모리 스택에 추가
    SPush(s, tmp);
 
    return tmp;
}
 
void RemoveAllNodeAuto(stack* s)
{
    struct node* rtarget = NULL;
    ShowStack(s);
    while (!SIsEmpty(s))
    {
        rtarget = SPop(s);
        free(rtarget);
        ShowStack(s);
    }
    return;
}
 
// 큐 자료구조 관련 함수
 
void QueueInit(queue* q)
{
    q->front = q->rear = NULL;
    q->count = 0;
    return;
}
int QisEmpty(queue* q)
{
    if (q->front == NULL)
        return TRUE;
    else
        return FALSE;
}
struct node* Dequeue(queue* q)
{
    struct node* target;
    // 기본 동작 - 비었는가 확인 -> 삭제 -> 인덱스 변경
    if (QisEmpty(q))
    {
        printf("Queue가 이미 비었습니다.\n");
        return NULL;
    }
    // target 추출
    target = q->front;
 
    // front를 맨 앞 노드의 다음 노드로 변경
    q->front = q->front->next;
 
    // 카운트 감소
    q->count--;
    return target;
}
void Enqueue(queue* q, stack* s, int data)
{
    // 노드 생성
    struct node* new_node = CreateNodeAuto(s); // 추가되는 노드가 항상 마지막이 된다.
    new_node->data = data;
    new_node->next = NULL;
 
    ShowStack(s);
 
    if (QisEmpty(q)) // 노드가 처음 추가하는 노드라면 front와 rear 모두 갱신
    {
        q->front = q->rear = new_node;
    }
    else // 노드가 처음 추가하는 노드가 아니라면 rear만 갱신
    {
        q->rear->next = new_node;
        q->rear = new_node;
    }
 
    // 카운트 증가
    q->count++;
    return;
}
 
void ShowQueue(queue* q)
{
    struct node* index = NULL;
    printf("{(r:%p/f:%p)} : ", q->rear, q->front);
    for (index = q->front; index != NULL; index = index->next)
        printf("[%p(%d)]", index, index->data), putc('-', stdout);
    putc('\n', stdout);
    return;
}
 
 
 
 
 
 
 
cs

 

728x90
반응형
728x90
반응형

 이전 포스팅 때, 두 개념을 언급했다.

Level-Trigger와 Edge Trigger의 차이를 그냥 Epoll 함수 공부를 할 때에는 잘 몰랐는데, 아래와 같은 상황에서 다시 생각해보게 되었다.

select 로 넘어온 디스크립터 변화의 갯수만큼 처리를 하지 않고 그냥 넘어가면 어떻게 될까?

라는 의문에서 부터 시작 되었다. 

Level Trigger 란? 

아래부터는 윤성우, 『열혈 tcp/ip 소켓 프로그래밍』, 오렌지미디어(2009), p376-p377. 이렇게 인용이 됩니다

윤성우 선생님/교수님의 책에 잘 정리가 되어 있어서, 비유 예시를 인용합니다. 내용을 좀 바꾸려다가 인용하는거 그냥 쓸께유 ㅋㅋ

아들 : 엄마 세뱃돈으로 5,000원 받았어요.
엄마 : 훌륭하구나

아들 : 엄마 옆집 숙희가 떡볶이 사달래서 사줫더니 2,000원 남았어요.
엄마 : 장하다 우리 아들 ~

아들 : 엄마 변신가면 샀더니 500원 남았어요.
엄마 : 그래 용돈 다 쓰면 굶으면 된다!

아들 : 엄마 여전히 500원 갖고 있어요. 굶을 수 없잖아요
엄마 : 그래 매우 현명하구나!

아들 : 엄마 여전히 500원 갖고 있어요. 끝까지 지켜야지요.
엄마 : 그래 힘내거라!

레벨 트리거의 핵심은 굵을 글씨이다.

5,000원이 들어온 이후에는 더 이상 돈이 발생하거나 더해지지 않았다.
>> 5,000원 어치의 이벤트 변화가 발생했다라고 생각하면 된다.

500원에서 계속 변화가 없음에도 불구하고 500원이 있다고 알란다.
>> 파일 디스크립터의 변화가 없더라도 처음에 들어온 것에 비해 남아있기만 한다면 계속해서 이벤트가 등록된다.

이러한 동작 방식이 레벨 트리거 방식이다. 

select 로 넘어온 디스크립터 변화의 갯수만큼 처리를 하지 않고 그냥 넘어가면 어떻게 될까?

그러므로 위와 같은 의문은 잘 해결된다. 등록된 이벤트만큼 처리하지 않더라도 이벤트가 다시 알려지기 때문에 당장 바로 처리하지 않아도 해당 이벤트가 무시되는 것이 아니라, 시간이 흘러감에 따라 처리할 수 있다는 뜻이다. 

그렇다면 엣지 트리거 방식은 무엇일까?

 Edge Trigger 란?

아래부터는 윤성우, 『열혈 tcp/ip 소켓 프로그래밍』, 오렌지미디어(2009), p376-p377. 이렇게 인용이 됩니다

아들 : 엄마 세뱃돈으로 5,000원 받았어요.
엄마 : 음 다음엔 더 노력하거라.(근데 과연 무슨 노력을 해야하는 것일까...? ㅋㅋㅋㅋ)

아들 : ..........................................
엄마 : 말 좀 해라! 그 돈 어쨌냐? 계속 말 안 할거냐?

딱 한 번 5,000원이 들어왔을 때, 이벤트가 등록되고, 그 이후에는 아무리 계속 남아있다고 해도 이벤트가 추가로 등록되지 않는다.

이런 차이가 있다.

다음 번에는 Select 모델의 한계에 대해서 포스팅 할 예정이다.

728x90
반응형
728x90
반응형

내가 퀵 정렬까지 공부할 줄은 몰랐(었)다. 역시 차근차근 해 놓는게 중요하다. 더불어 포스팅도 그 때 그 때 하지 않으면 이렇게 개 고생한다.

어찌됐건 오늘은 퀵 정렬이다.

나는 이게 퀵 정렬이 겁나 빨라서 퀵 정렬인가 했는데, 맞다. 겁나 빠른 퀵 정렬 한 번 기록해둔다.

퀵 정렬이란?

퀵 정렬은 이름을 통해서는 대충 어떤 정렬인지 짐작하기가 힘들다. 뭐 병합 정렬과 같은 경우는 뭔가 병합 해나가겠지 싶었는데 퀵 정렬은 아니다. 그래서 퀵 정렬이 무엇인가 라기 보다는 퀵 정렬의 작동 원리를 기록하는게 나의 인생에, 그대의 인생에 더 유익하지 않을까 싶다.

퀵 정렬은 병합 정렬과 매우 비슷하게, 쪼개는 것이 그 기본으로 깔려 있는 정렬이다. 또 쪼개기 시작한다는거다. 쪼갠다는 것의 의미는 병합 정렬에 잘? 설명되어 있는진 모르겠고 애타게 설명을 적어 놓았다.

https://typingdog.tistory.com/122?category=924874

 

C Data Structure - 병합 정렬

이번에는 병합 정렬이다. 이게 생각보다 간단하긴 한데 복잡하다(?) 일단 시작한다. 병합 정렬이란? 병합 정렬의 기본은 다음과 같다. 1. 배열을 원소 단위로 쭉~~ 나눴다가 2. 다시 단계적으로 정

typingdog.tistory.com

퀵 정렬의 주된? 사상?은 이렇다. 

1. 배열을 쪼개는 것과 동시에 정렬하는 것이 아니다
2. 그렇다고 해서 앞/뒤 비교를 하며 정렬을 한다고 생각하면 안된다.
3. 어떤 기준에 맞는 '하나'의 요소의 정렬된 위치를 찾고, 고정한 뒤, 그 위치를 중심으로 양 옆 두개로 쪼개는 것이다. 이 때, 고정된 요소는 이미 올바른 정렬 위치를 찾았으니 정렬 대상에서 제외한다.

그러니까 결국에는 주어진 범위에 한해서 어떤 기준에 의거하여 단 하나의 요소의 올바른 정렬 위치를 찾아나가고 그 위치를 기준으로 양 옆으로 쪼개는 것을 반복해나간다고 생각하면 된다.

사과해야겠다. 이게 무슨 말이냐? 그림으로 기록해야겠다.

이런 배열이 있다.

이 배열을 아주 오도방정을 떨면서 왔다갔다 할 이상한 인덱스들을 소개하겠다.

자, 그림 내에 설명을 기록했지만, 추가 설명이 필요한 부분을 기록하겠다. 

일단, pivot은 비교의 기준이라고 했는데 꼭 이 기준을 맨 왼쪽에 둘 필요도 없다. 다만 이런 기준이 어디에 있긴 있어야한다는 소리인데, 오름차순 정렬의 경우 맨 왼쪽에 많이 쓴다고 생각한다고 생각한다....ㅋㅋ

left와 right는 무조건 배열 전체 중 인덱스 0과 인덱스 끝을 나타내는게 아니라 주어진 범위 내에서 가장 왼쪽과 오른쪽을 나타내는 것이기 때문에, 물결 표시로 강조를 했다.

기본적인 동작은 다음과 같다.

row를 먼저 쫙~ 증가시킨 다음에 2번 상태를 만들고, high를 쫙~ 감소시켜서 그 이후에 교환을 하는 식이 베스트다.

 

low와 high가 서로를 넘어선 경우이다. 이 경우에는 더 진행해도 (row의 입장에서)pivot보다 작은 값이, (high의 입장에서)pivot보다 큰 값이 나올리가 없기 때문이다. -> 우리는 지금 pivot을 기준으로 작은 값들을 왼쪽으로 몰아왔고, 큰 값들을 오른쪽으로 몰아왔다. 눈으로 잘봐라.

그러면 이제 몰려있는 곳의 경계가 있을 것이다. 3과 6 사이이다. 그 사이에 pivot 값을 넣고 뒤에 값들을 하나씩 땡기고 할 것 없이 high의 값과 pivot의 값을 바꾸면 정확하게 맞아떨어진다. 

pivot 기준으로 왼쪽은 pivot보다 작은 값. 오른쪽은 pivot보다 큰 값.

개쩐다.. 5가 고정 되었다. 이걸 재귀로 왼쪽편, 오른쪽 편 쫘르륵 실행해주면 알아서 각각의 pivot들이 제 자리를 찾아 정렬된 결과를 딱 보일 수 있을 것이다. 

이런 방법을 어떻게 생각했단 말인가 진짜..

코드 및 실행 결과

----

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
 
#include <stdio.h>
 
void SwapData(int* lval, int* rval)
{
    int temp;
    temp = (*lval);
    (*lval) = (*rval);
    (*rval) = temp;
    return;
}
void ShowArrayArea(int arr[], int beginint end)
{
    int i = 0;
    for (i = begin; i <= end; i++)
        printf("%d ", arr[i]);
    fputc('\n', stdout);
    return;
}
int Partition(int arr[], int left, int right)
{
    int row = left + 1, high = right; // 1개일 때에는 이 부분 때문에 그냥 한 개에 해당하는 인덱스가 반환됨.
    int pivot = left;
 
    printf("타겟 범위 내 값 나열 : \n");
    ShowArrayArea(arr, left, right);
 
    while (row <= high) // while ((high - row) > 0)
    {
        while (arr[row] <= arr[pivot] && row <= right) // <= 와 && 이후 조건인 이유는 동일한 값들이 왔을 때 처리를 위함. 
            row++;
        while (arr[high] >= arr[pivot] && high >= (left+1)) // >= 와 && 이후 조건인 이유는 동일한 값들이 왔을 때 처리를 위함.
            high--;
 
        if (row <= high)
            SwapData(&arr[row], &arr[high]);
    }
    SwapData(&arr[high], &arr[pivot]);
    return high;
}
void QuickSort(int arr[], int left, int right)
{
    if (left <= right) 
    {// 재귀의 탈출 조건 -> right가 left를 역행하면 진행하면 안됨. left와 right가 동일할 때 1개 요소 레벨이기 때문에.
        int pivot = Partition(arr, left, right);
        QuickSort(arr, left, pivot - 1);
        QuickSort(arr, pivot + 1, right);
    }
    return;
}
 
int main(void)
{
    int arr[9= { 1918151315829 };
    // int arr[9] = { 5,5,5,5,5,5,5,5,5 };
    int i = 0;
 
    QuickSort(arr, 08);
 
    for (i = 0; i < 9; i++)
        printf("%d ", arr[i]);
    fputc('\n', stdout);
    return 0;
}
cs

----

QuickSort 함수에서 ' if (left <= right) ' 이 조건! 이 조건은 위 퀵 정렬 과정에서 row와 high 가 서로 넘어서는 경우와는 완전 다른 조건이다. 해당 조건은 쪼개짐의 범위에 대한 조건으로 left와 right가 서로를 넘어서는 것은 이미 요소 하나 단위까지 쪼개고 난 이후라는 의미이다.

잘 정렬되었다. 

병합 정렬에서 재귀에 대해 한 번 설명을 써본 탓에 이번엔 정신이 온전하다.

퀵 정렬의 성능

퀵 정렬의 빅오는 pivot을 어떤 것을 선택하느냐에 따라서 다르다. 잘 선택한 경우

최선의 경우 : O(n log 2 n) - 2는 아래 첨자 2이다.
최악의 경우 : O(n^2) 

최선의 경우를 잘 뽑도록 pivot을 데이터 세트의 특성 상 잘 뽑아야하고, 데이터의 이동이 적기 때문에 매우 효율적이고 빠른 알고리즘이라고 생각하면 되겠다.

728x90
반응형
728x90
반응형

공부하고 나면 그 때 그 때 정리해야한다는 것을 뼈저리게 느끼는 시작이다..

이번에 정리할 내용은 단순 배열 리스트이다. 다만, 그냥 배열을 쫘르륵 순회하고, 삭제 띡 하고, 추가하고 그러면 일반 배열 사용법을 정리하는 것과 다를 바가 없으므로, 약간 형식이 있는 리스트 틱한 배열로 만들어 정리한다ㅋㅋ

일단은 정의와 특징, 장 단점을 간단하게 상기시키며 넘어가도록 하겠다.

단순 배열 리스트란?

말 그대로 단순한 배열이다. 그런데 리스트라는 자료구조 형태이므로 메모리 공간 뿐만 아니라 삽입, 삭제, 탐색에 대한 함수를 추가 보완함으로써 하나의 자료 구조로 완성을 시킬 셈이다.

배열

배열 리스트의 장점

Random Access, 임의 접근이 인덱스를 기반으로 가능하기 때문에 데이터의 참조가 쉽다.  O(1) <- 개쩐다ㄷㄷ

배열 리스트의 단점

배열의 길이가 초기에 결정되며, 이 길이는 변경이 불가능하다.
삭제 과정에서 데이터의 이동이 매우 많이 일어난다.
탐색할 경우 최악의 경우에는 O(n)이다. <- 매우 성능이 나오지 않는 것이다. 데이터의 수에 매우 정비례하여 시간이 늘어나기 때문이다.

코드 구현 및 실행

---

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
#include <stdio.h>
#define MAX_LIST_SIZE 20
 
struct List
{
    int insert_index;
    int search_index;
    int arr[MAX_LIST_SIZE];
};
 
void ListInit(struct List* list)
{
    int i = 0;
    list->insert_index = 0// 값을 추가할 때 사용하는 인덱스 -> 모든 값은 0 ~ (insert_index-1) 까지만 유효함!
    list->search_index = 0// 탐색을 위한 순회 시 사용하는 인덱스
 
    // 모두 0으로 초기화
    for (i = 0; i < MAX_LIST_SIZE; i++)
        list->arr[i] = 0;
    return;
}
void LInsert(struct List* list, int value)
{
    if (list->insert_index >= MAX_LIST_SIZE) 
    {
        printf("저장이 불가능합니다.\n");
        return;
    }
    list->arr[list->insert_index++= value;
    return;
}
int LCount(struct List* list) // 현재 추가된 수
{
    return list->insert_index;
}
 
int LFirst(struct List* list, int* data) // 첫 번째 요소에 인덱스를 위치시키는 역할이라고 보면 된다.
{
    if (list->insert_index >= 1// 한 번이라도 값이 추가된 적이 있는 경우에만 사용.
    {
        list->search_index = 0;
        *data = list->arr[list->search_index];
        return 1;
    }
    // search_index를 증가시키지 않는다. 필요하다면 증가를 미리 시키고 접근해야함.
    return 0;
}
int LNext(struct List* list, int* data) // LFirst 함수에 이어 바로 다음 요소에 인덱스를 위치시키는 역할이라고 보면 된다.
{
    if ((list->search_index + 1< list->insert_index) // 접근해야하는 인덱스(search_index+1)가 유효한 인덱스 범위 내에 존재한다면
    {
        *data = list->arr[++(list->search_index)];
        return 1;
    }
    return 0;
}
void LRemove(struct List* list) // 현재 위치한 인덱스의 값을 제거하고, 빈 요소 자리를 채우는 작업까지 진행한다.
{
    int target = 0;
 
    for (target = list->search_index; target < (list->insert_index - 1); target++// 유효 인덱스의 바로 전 인덱스까지만 가서 땡겨오면 끝.
        list->arr[target] = list->arr[target + 1];
    list->search_index--;
    list->insert_index--;
 
    printf("\n");
 
    return;
}
 
int main(void)
{
    struct List list;
    int data;
    ListInit(&list);
 
    LInsert(&list, 11);
    LInsert(&list, 11);
    LInsert(&list, 22);
    LInsert(&list, 22);
    LInsert(&list, 33);
 
 
    if (LFirst(&list, &data))
    {
        printf("%d ", data);
        while (LNext(&list, &data))
            printf("%d ", data);
    }
 
    if (LFirst(&list, &data))
    {
        if (data == 22)
            LRemove(&list);
        while (LNext(&list, &data))
        {
            if (data == 22)
                LRemove(&list);
        }
    }
 
    if (LFirst(&list, &data))
    {
        printf("%d ", data);
        while (LNext(&list, &data))
            printf("%d ", data);
    }
 
    printf("\n\n");
    return 0;
}
cs

---

 

728x90
반응형

+ Recent posts