반응형
728x90
반응형

C++에서도 자바스크립트 처럼 익명 함수, 클로져 쓰듯, 사용할 수 있다는게 웹 개발을 해봤던 나로서는 너무 신기하고 놀라웠다. 함수처럼 사용되는 객체 등으로 이미 지원이 되긴 했지만, 

위와 같이 준비를 해야 사용이 가능하므로 불편하긴 마찬가지긴 하다. ( 다른 언어에 비하면 )

자 그럼, 람다 표현식은 다음과 같다.

형식은 위와 같은데, 캡처라는 부분이 사실 처음에 잘 와닿지 않았다. 그런데 자세히 보니, 캡처란? 람다 표현식의 바디 내에서 사용할 외부 변수를 지정해주는 구간일 뿐이었다.

바로 예시를 들겠다.

 

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
#include <iostream>
#include <string>
 
int content = 1;
 
int main(void)
{
    std::string str = "IOSTREAM";
    int zero = 0;
    int plus999 = 999;
 
    std::cout << [zero, plus999, str](int a, int b) -> int { 
        std::cout << str << std::endl; content = 400return ((a + b) * zero + plus999); 
    }(5149<< std::endl;
    std::cout << "[zero, plus999, str] content = " << content << std::endl;
 
    std::cout <<  [=](int a, int b) -> int {
        std::cout << str << std::endl; content = 200return ((a + b) * zero + plus999);
    }(5149<< std::endl;
    std::cout << "[=] content = " << content << std::endl;
 
    std::cout <<  [&](int a, int b) -> int {
        std::cout << str << std::endl; content = 400return ((a + b) * zero + plus999);
    }(5149<< std::endl;
    std::cout << "[&] content = " << content << std::endl;
 
    return 0;
}
cs

728x90
반응형
728x90
반응형

몸살 감기로 아팠다. 그래서 기념으로 좀 쉬고, 일어나서 포스팅한다ㅠ

먼저, C++ 언어에서의 L-Value와 R-Value에 대해서 정리할 필요가 있다. 너무나도 당연하다고 생각해왔던 것들을 새삼스럽게 정리를 하려고 보니 좀 어려운 면이 없지 않아 있다.

L-Value와 R-Value란?

위의 그림에서와 같이 대입 연산자를 기준으로 왼쪽의 특성을 갖는 요소는 무조건 L Value라고 한다. 이 말은 오른쪽에 위치해도 L Value라고 할 수 있다는 소리다. 그 반대로 대입 연산자를 기준으로 오른쪽의 특성을 갖는 요소는 무조건 R Value라고 한다. 그러나 R Value는 대입 연산자 기준으로 왼쪽에 올 수 없다.

아마 특징과 예시를 보면 L Value와 R Value가 무엇인지 알 수 있을 것이다.

L-Value와 R-Value의 특징

먼저, 그 특징이다.

L value는 보통 변수를 대부분의 예로 들 수 있으며, R value는 임시 객체 혹은 주소 +, - 등의 연산 결과 등이 있다.

그런데 C++에서는 R Value에서 임시 객체라고 하는 것들, 요 부류에 해당하는 것들이 꼭 문제가 된다. 왜냐하면 다음과 같은 경우를 보자.

R-Value에서 문제가 되는 부분

대충 이러한 클래스가 있다.

이 클래스를 기반으로 여러 가지 생성과 동시에 초기화를 할 것이다. 그 생성과 동시에 초기화를 할 때, 두 가지 경우를 보자.

1번의 설명대로, 생성과 동시에 초기화하는 두 경우는 문법 상, 복사 생성자가 호출되는 것이 맞다. 왜냐하면 초기화할 값의 대상으로 객체가 넘어오기 때문이다. 위의 클래스에서처럼 인스턴스화 할 때 동적 할당을 해야하는 경우에는 깊은 복사가 필수이므로, 복사 생성자가 호출되어야 한다.

그런데 자세히 보면 효율적이지 못한 부분이 있다.

AAA b(AAA("ccc")); 이 문장의 경우에는 분명 괄호 안의 AAA("ccc")는 R value 로 임시 객체이며 곧 사라진다. 그렇기 때문에 b 내부의 str을 또 동적 할당하고~, 임시 객체의 str 내용을 또 b의 str에 복사하는 이런 작업을 하지 말고, 2번 처럼 그냥 임시 객체의 str의 주소 값을 b의 str 에 넣어버리고 임시 객체는 그냥 소멸해버리면 되는 것 아니냐?? 어차피 사라질 놈이니까

만약에 깊은 복사가 이루어지는 것이 뭐 단순 str 같은 것들이 아니라 100MB 정도되는 동영상이라고 생각해보자. 객체 하나 복사하는데 100MB 짜리가 막 두개 였다가 하나였다가 메모리가 아주 들쑥날쑥 할 것이다.

그러나 실제로는 2번과 유사하긴 하지만 조금 다르게 복사 생략(copy elision)이라는 현상이 발생한다.

 

또한 이런 경우도 존재한다.

위와 같은 경우도 결국 s 혹은 z 객체는 곧 사라질 객체이기 때문에 새로 생성되는 임시 객체에 값을 전달할 때, 복사 생성자가 호출된다. 

위 경우들 모두 어차피 사라질 객체들이므로, 객체 내의 깊은 복사가 필요한 부분은 그냥 사라질 객체 내의 str 변수의 메모리 주소만 보존하여 가지고 와서 갱신해주고, 객체는 소멸 시켜버리면 된다.

그래서 나온 개념이 r-value 레퍼런스이동 생성자이다.

r-value 레퍼런스, 이동 생성자

205라인은 const & 참조자를 이용해 예외적으로 R-value를 잡을 수 있었다. 그러나 [11]부터는 211 라인에서 처럼 r - va lue를 잡을 수 있는 r value 레퍼런스가 등장했는데, 이를 이용하여 r-value가 해당 라인이 종료된 후에도 소멸되지 않고 잡아둘 수 있을 뿐만 아니라, 값의 변경까지 허용한다!

이를 이용하여 만들어진 것이 이동 생성자이다!

이동 생성자와 복사 생성자를 함께 보도록 하자.

위의 이동 생성자로 인해 다음과 같이 복사 생성자의 호출을 막고, 메모리가 새로 할당되고, 복사하고 이런 과정을 막았다. 이는 매우 효율적이다!

결론적으로 이동 생성자는 다음과 같이 보면 된다.

C++ 11에서는 바로 이러한 이동 생성자라든가, R value 레퍼런스 등의 문법 덕분에 자원 문제가 해결되었다. 아래의 내용을 기록하려고 위를 기록한 것이다 결국..

STL 컨테이너 등을 사용할 때, 위처럼 대입하는데에만 복사 생성자가 2번 넘게 호출되며 메모리가 들쑥날쑥할텐데 이동 생성자라든가, 복사 생략 등으로 인해서 자원 효율이 좋아진다는 점이다.

RVO & NRVO

RVORetrun Value Optimization 의 약자이고, NRVONamed Return Value Optimization의 약자이다.

뭔가 리턴 값을 최적화 한다는 소리인 것 같은데 다음의 예제를 보자.

위의 두 경우의 함수가 있다. 앞에는 컴파일러에 의해 NRVO 가 적용되는 함수이고, 뒤에는 컴파일러에 의해 RVO가 적용되는 함수이다.

이를 호출할 경우, 두 경우 반환하는 형태가 모두 참조형으로의 반환이 아니기 때문에 복사 생성자가 호출된다!

그러나 실제로는 그렇지 않다.

그렇다고 해서 이동 생성자가 호출된 것도 아니고, 실제로 이동 생성자가 호출되지도 않는다

RVO, NRVO는 반환되는 값에 대해서 최적화하여 복사 생성자가 호출이 되지 않게 하는 것이다. 이동 생성자나 복사 생성자보다 더 우선순위 있게 실행이 된다. 당연하게도 최적화기 때문에 우선순위가 높은 것이다.

그러면 위에서 이동 생성자가 호출된 NRVO 함수 호출은 무엇이었을까?

위의 예시처럼 NRVO가 실행이 안 되는 경우에는 이동 생성자가 호출되는 것이다. 

근데 이게 컴파일러마다 조금씩 다르다 VS 2019 와 g++ 컴파일러에서는 다르게 표현되는 경우도 있고 한 것 같다. 하 이게 정말 별거 아닌 개념인 것 같은데 컴파일러마다 다르고, 이미 올라와있는 11 내용이랑 17, 20에서 바뀐 표준안이랑 살짝 충돌되는 경우들도 있어서 알아보느라 너무 힘들었다 정말.

아래는 사용한 코드이다. 

 

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
#include <iostream>
#include <vector>
#include <cstring>
#pragma warning(disable:4996)
 
class AAA
{
private:
    char* data;
public:
    AAA(const char* data)
    {
        this->data = new char[1000000];
        std::strcpy(this->data, data);
        std::cout << "Call AAA(char * data)![data:" << this->data << "] / this : [" << this << ']' << std::endl;
    }
    AAA(const AAA& ref)
    {
        this->data = new char[1000000];
        std::strcpy(this->data, ref.data);
        std::cout << "Call AAA(const AAA& ref)![data:" << this->data << "] / this : [" << this << ']' << std::endl;
    }
    AAA(AAA&& ref) : data(ref.data)
    {
        ref.data = nullptr;
        std::cout << "Call AAA(AAA&& ref)![data:" << this->data << "] / this : [" << this << ']' << std::endl;
    }
    ~AAA()
    {
        if (this->data != nullptr)
        {
            std::cout << "Call ~AAA()![data:" << this->data << "] / this : [" << this << ']' << std::endl;
            delete[]this->data;
        }
        else
        {
            std::cout << "Call ~AAA()! / this : [" << this << ']' << std::endl;
        }
    }
 
    void ShowData(void)
    {
        if (this->data == nullptr)
            std::cout << "data : " << std::endl;
        else
            std::cout << "data : " << this->data << std::endl;
    }
};
 
AAA FuncNrvo(void)
{
    AAA s("ssssssssssssssssssssssssssssss");
    return s;
}
AAA FuncNrvoS(int a)
{
    AAA s("ssssssssssssssssssssssssssssss");
    AAA z("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz");
    if (a == 100)
    {
        return s;
    }
    else
    {
        return z;
    }
}
 
AAA FuncRvo(void)
{
    return AAA("ssssssssssssssssssssssssssssss");
}
AAA FuncRvoS(int a)
{
    if (a == 100)
    {
        return AAA("ssssssssssssssssssssssssssssss");
    }
    else
    {
        return AAA("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz");
    }
}
 
int main(void)
{
    std::cout << "1. 일반 생성자 호출" << std::endl;
    {
        AAA a = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
        AAA b("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "2. 복사 생성자 호출" << std::endl;
    {
        AAA a = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
        AAA b("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
 
        AAA c = a;
        AAA d(b);
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "3. 임시 변수 r-value의 생성 - 라인이 끝나면 소멸된다." << std::endl;
    {
        AAA("cccccccccccccccccccccccccccccc");
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "4. l value 에 r value 대입 - 생성과 동시에 초기화 및 라인이 끝났음에도 소멸되지 않음." << std::endl;
    {
        AAA e(AAA("dddddddddddddddddddddddddddddd"));
        std::cout << "&e : " << &<< std::endl;
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "5. r-value를 const & 를 통해서 가리킴 - 라인이 끝났음에도 소멸되지 않음." << std::endl;
    {
        const AAA& f(AAA("eeeeeeeeeeeeeeeeeeeeeeeeeeeeee"));
        std::cout << "&f : " << &<< std::endl;
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "6. FuncNrvo() 함수의 AAA 반환으로 생성과 동시에 초기화." << std::endl;
    {
        AAA g(FuncNrvo());
        std::cout << "&g : " << &<< std::endl;
        g.ShowData();
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "7. FuncNrvoS() 함수의 AAA 반환으로 생성과 동시에 초기화." << std::endl;
    {
        AAA h(FuncNrvoS(11));
        std::cout << "&h : " << &<< std::endl;
        h.ShowData();
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "8. l-value를 r-value화 하여 생성과 동시에 초기화." << std::endl;
    {
        AAA a = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
 
        AAA i(std::move(a));
        std::cout << "&i : " << &<< std::endl;
        i.ShowData();
        a.ShowData();
 
        FuncNrvo();
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "10. 그냥 FuncNrvoS()만 호출" << std::endl;
    {
        FuncNrvoS(19);
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
 
    std::cout << "11. 그냥 FuncRvo()만 호출" << std::endl;
    {
        FuncRvo();
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "12. 그냥 FuncRvoS()만 호출" << std::endl;
    {
        FuncRvoS(19);
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
 
    std::cout << "13.FuncRvo()을 받으며 호출" << std::endl;
    {
        AAA j(FuncRvo());
        std::cout << "&j : " << &<< std::endl;
 
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "14. FuncRvoS()을 받으며 호출" << std::endl;
    {
        AAA k(FuncRvoS(19));
        std::cout << "&k : " << &<< std::endl;
 
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
    // 복사 생성자를 호출하지 않으며, AAA(100)의 임시객체는 즉시 소멸하지도 않는다.
    // -> e 자체를 AAA(100)으로 만들어진 객체로 취급
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    const int& lref = 100;
    //lref = 101;
    std::cout << lref << std::endl;
 
    std::cout << "--------------------------" << std::endl;
 
    int&& rref = 100;
    rref = 101;
    std::cout << rref << std::endl;
 
    return 0;
}
cs
728x90
반응형

'프로그래밍응용 > Modern & STL' 카테고리의 다른 글

함수 포인터를 대체하는 std::function  (0) 2021.02.08
Lambda Expression  (0) 2021.01.27
자료형 추론 auto / 범위 기반 for  (0) 2021.01.21
Iterator 사용 방법 예시  (0) 2021.01.05
Priority Queue  (0) 2021.01.04
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
반응형
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
반응형

 

다음과 같은 배열들을 순회할 것인데, 

1
2
3
4
int numArr[10= { 95732923, };
std::vector<int> numVec(&numArr[0], &numArr[5]);
const char* strArr[3= { "I'm Shyoo""Dog""Mouse" };
 
cs

전통적인(?), 일반적인 순회 방법과 auto 자동 추론을 이용한 반복과 범위 기반 반복을 각각 실험해본다.

일반적인 순회

1
2
3
4
5
6
7
8
9
10
11
12
13
cout << "[기존 배열 순회 방법]" << endl;
 
for (int i = 0; i < 10; i++)
    cout << numArr[i] << ' ';
cout << endl;
 
for (std::vector<int>::iterator it = numVec.begin(); it != numVec.end(); it++)
    cout << *it << ' ';
cout << endl;
 
for(char ** p = (char **)strArr; p <= strArr+2; p++// 억지로 계산하는게 꺼림칙하긴 하지만..
    cout << *<< ' ';
cout << endl;
cs

일반적인 배열, 벡터, 포인터 배열을 순회하는 평범한 코드이다.

자료형 자동 추론을 통한 순회

1
2
3
4
5
6
7
8
9
10
11
12
13
cout << "[자료형 자동 추론]" << endl;
 
for (auto i = 0; i < 10; i++)
    cout << numArr[i] << ' ';
cout << endl;
 
for (auto it = numVec.begin(); it != numVec.end(); it++)
    cout << *it << ' ';
cout << endl;
 
for (auto p = (char**)strArr; p <= strArr + 2; p++)
    cout << *<< ' ';
cout << endl;
cs

auto 키워드를 통해서 인덱스 역할을 하는 변수의 자료형을 알아서 탐지한다.. 너무 편하다 정말

범위 기반 반복 for을 통한 순회

1
2
3
4
5
6
7
8
9
10
11
12
13
cout << "[범위 기반 for문을 이용한 배열 순회]" << endl;
 
for (auto element : numArr)
    cout << element << ' ';
cout << endl;
 
for (auto it : numVec)
    cout << it << ' ';
cout << endl;
 
for(auto p : strArr)
    cout << p << ' ';
cout << endl;
cs

다른 언어에서 지원하는 것처럼 c++도 지원한다. 자바스크립트나 파이썬의 for in과 같고, 자바의 for each 구조와 같다.

 

전체 코드 및 실행 결과

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
#include <iostream>
#include <vector>
 
using std::cout;
using std::endl;
using std::cin;
 
int main(void)
{
    int numArr[10= { 95732923, };
    std::vector<int> numVec(&numArr[0], &numArr[5]);
    const char* strArr[3= { "I'm Shyoo""Dog""Mouse" };
 
    cout << "[기존 배열 순회 방법]" << endl;
    for (int i = 0; i < 10; i++)
        cout << numArr[i] << ' ';
    cout << endl;
 
    for (std::vector<int>::iterator it = numVec.begin(); it != numVec.end(); it++)
        cout << *it << ' ';
    cout << endl;
 
    for(char ** p = (char **)strArr; p <= strArr+2; p++// 억지로 계산하는게 꺼림칙하긴 하지만..
        cout << *<< ' ';
    cout << endl;
 
 
    cout << "[자료형 자동 추론]" << endl;
    for (auto i = 0; i < 10; i++)
        cout << numArr[i] << ' ';
    cout << endl;
 
    for (auto it = numVec.begin(); it != numVec.end(); it++)
        cout << *it << ' ';
    cout << endl;
 
    for (auto p = (char**)strArr; p <= strArr + 2; p++)
        cout << *<< ' ';
    cout << endl;
 
    cout << "[범위 기반 for문을 이용한 배열 순회]" << endl;
 
    for (auto element : numArr)
        cout << element << ' ';
    cout << endl;
 
    for (auto it : numVec)
        cout << it << ' ';
    cout << endl;
 
    for(auto p : strArr)
        cout << p << ' ';
    cout << endl;
 
 
    return 0;
}
cs

실행이 기가 막히게 잘되는 것을 볼 수 있다.

 

728x90
반응형

'프로그래밍응용 > Modern & STL' 카테고리의 다른 글

Lambda Expression  (0) 2021.01.27
R-Value, Copy Elision, 이동 생성자, RVO, NRVO  (0) 2021.01.26
Iterator 사용 방법 예시  (0) 2021.01.05
Priority Queue  (0) 2021.01.04
Queue  (0) 2021.01.04
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
반응형

자 오늘도 학습했던 원형 양방향 연결 리스트를 정리할 때가 됐다. 이게 정리를 바로바로 안 하다보니 후, 코드를 다시 이해해야한다. 뭐 복습도 되고 참 좋다;;;

먼저 오늘 할 것은

원형 - 무한 순회가 가능한 -> 그래서 순회에 조건이 꼭 붙는,
양방향 - 노드의 양쪽 링크가 존재하는 -> 그래서 원형 순회 실현이 가능한
연결 리스트

를 포스팅 한다.

원형이라고 해서 개인적으로 위와 같은 형태로  생각하면 혼란만 가중할 것이라고 생각한다. 아래와 같이 말이다.

그래서 우리는 머릿속의 형태를 정해놓고 시작해보고자 한다.

이러한 형태 임을 꼭 기억하고 있어야 한다.

노드 삽입 연산

삽입 연산은 노드가 없을 때와 있을 때로 나눈다.

크 따로 부가 설명이 필요 없을 것 같다ㅋ 

노드 삭제 연산

삭제는 간단하다. 그림 하나로 표현 가능하다.

삭제의 기본이 되는 부분

 

그 이외에 순회 연산

순회 연산은 그냥 노드의 링크를 순방향/역방향으로 타는 인덱스 포인터를 만들어서 순회를 하면 된다. 다만, 원형 연결 리스트인 만큼 무한 반복에 빠질 수 있기 때문에 아래와 같은 조건을 넣어 무한 반복에서 탈출하도록 해야한다.

위 코드는 순방향 순회, 역방향 순회 모두 마찬가지로 해당되는 부분이다.

 

메모리 관리 부분

해당 코드에서 또한 메모리 관리 부분이 따로 있기 때문에 삭제 연산 시에도 바로 메모리를 제거 하지 않고 프로그램 종료 전에 한꺼번에 모두 free 시키도록 되어 있다.

클래스로 stack 컨테이너 어댑터로 아래와 같이 구현되어 있다.

코드 및 실행 결과

 

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
#include <iostream>
#include <stack>
#include <stdio.h>
 
using namespace std;
 
// 구조체 존
struct node {
    int data;
    struct node* prev;
    struct node* next;
};
 
struct list {
    struct node* head;
    struct node* ci; // current index
    int count;
};
 
// 클래스 존
class NodeMemoryManager // 이 클래스 자체에 템플릿을 때려버리면 여러 타입에 대해서 관리할 수 있다ㅋ
{
private:
    int count; // 내부적인 교차 검증을 위한 count;
    stack<struct node*> allocated_node_address;
public:
    NodeMemoryManager()
    {
        this->count = 0;
    }
    ~NodeMemoryManager()
    {
        int free_count = 0;
        int stack_count = this->allocated_node_address.size(); // 제거하기 전 숫자.
 
        // 스택은 내부적으로 벡터니까 -> 임의 접근이 되지 않을까? -> 불가능 -> 그럴거면 벡터를 쓰지 왜 스택을 쓰나? 스택의 탄생 이유이다.
        while (!this->allocated_node_address.empty())
        {
            free(this->allocated_node_address.top()); // c에서 malloc 할 것이니, free 함수.
            this->allocated_node_address.pop();
            free_count++;
        }
        cout << "모든 메모리가 해체되었습니다[" << free_count << "/" << this->count << "(" << stack_count << ")]" << endl;
    }
    struct node* CreateNodeAuto(void)
    {
        struct node* tmp = (struct node*)malloc(sizeof(struct node));
        // 초기화
        tmp->data = 0;
        tmp->prev = NULL;
        tmp->next = NULL;
        // 스택 추가
        this->AddAllocatedNodeAddress(tmp);
        return tmp;
    }
    void AddAllocatedNodeAddress(struct node* addr)
    {
        this->allocated_node_address.push(addr);
        this->count++;
        return;
    }
    int GetCount()
    {
        return this->count;
    }
    int GetCountByStack()
    {
        return this->allocated_node_address.size();
    }
};
 
// 재정의 존
typedef struct list List;
 
// 함수 선언 존
void ListInit(List* list);
void LInsert(List* list, int data);
int LFirst(List* list, int* data);
int LNext(List* list, int* data, int non_exit);
int LPrevious(List* list, int* data, int non_exit);
void LRemove(List* list);
 
// 전역 변수 존
NodeMemoryManager nmm;
 
//메인 함수
int main(void)
{
    List list;
    int data;
 
    ListInit(&list);
 
    // 값 1 ~ 값 8 까지 추가.
    LInsert(&list, 1); LInsert(&list, 2); LInsert(&list, 3); LInsert(&list, 4); LInsert(&list, 5); LInsert(&list, 6); LInsert(&list, 7); LInsert(&list, 8);
 
    LFirst(&list, &data);
 
    // 한 칸 씩 이동
    printf("-------- previous zone \n");
    (LPrevious(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LPrevious(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LPrevious(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LPrevious(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LPrevious(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LPrevious(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LPrevious(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LPrevious(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LPrevious(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LPrevious(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LPrevious(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LPrevious(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LPrevious(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LPrevious(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
 
    printf("-------- next zone \n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
    (LNext(&list, &data, 1== 1) ? printf("[%d]\n", data) : printf("LFirst가 실행되지 않았습니다.\n");
 
    // 순회 및 삭제 처리
    printf("-------- remove zone \n\n");
    if (LFirst(&list, &data))
    {
        if (data == 2)
            LRemove(&list);
        while (LNext(&list, &data, 0))
        {
            if (data == 2)
                LRemove(&list);
        }
        while (LPrevious(&list, &data, 0))
        {
            if (data == 6)
                LRemove(&list);
        }
    }
 
    // 순회
    printf("-------- circuit zone \n");
    if (LFirst(&list, &data))
    {
        printf(" - [%p|%p(%d)|%p] - \n", list.ci->prev, list.ci, list.ci->data, list.ci->next);
        while (LNext(&list, &data, 0))
            printf(" - [%p|%p(%d)|%p] - \n", list.ci->prev, list.ci, list.ci->data, list.ci->next);
        while (LPrevious(&list, &data, 0))
            printf(" - [%p|%p(%d)|%p] - \n", list.ci->prev, list.ci, list.ci->data, list.ci->next);
    }
 
    printf("최종으로 남은 노드의 개수는? ( %d )\n", list.count);
 
    return 0;
}
 
// 함수 정의 존
void ListInit(List* list)
{
    // 헤더 생성 및 초기화
    list->head = NULL;
    // 카운트 초기화
    list->count = 0;
    // 인덱스 노드를 초기화
    list->ci = NULL;
 
    return;
}
 
void LInsert(List* list, int data)
{
    // 새 노드 생성
    struct node* new_node = nmm.CreateNodeAuto();
 
    // 새 노드 초기화
    new_node->data = data;
 
    if (list->head == NULL// 자료구조 자체가 비어있으면
    {
        new_node->prev = new_node->next = new_node;
    }
    else
    {
        // 새 노드의 양 링크를 연결.
        new_node->next = list->head; // 1. [새 노드] -> 기존 시작 노드 연결
        new_node->prev = list->head->prev; // 2. 기존 끝 노드 <- [새 노드] 연결
 
        // 양쪽 노드의 prev 링크들을 연결.
        new_node->next->prev = new_node; // 새 노드 <- [기존 시작 노드] 연결
        new_node->prev->next = new_node; // 새 노드 <- [기존 끝 노드] 연결
    }
 
    // 헤드와 빈 노드 연결
    list->head = new_node;
 
    // 노드 추가 완료 시, 카운트 증가
    list->count++;
 
    return;
}
 
int LFirst(List* list, int* data)
{
    // 헤드를 검사한다. 널이면 false 반환, 널이 아니면 진행
    if (list->head == NULL)
        return false;
 
    // 인덱스를 옮긴다.
    list->ci = list->head;
 
    // 데이터 참조하여 값을 변경
    *data = list->ci->data;
 
    // true 반환
    return true;
}
 
int LNext(List* list, int* data, int non_exit)
{
    // LFirst 가 실행되지 않은 경우는 false
    if (list->ci == NULL)
        return false;
 
    // 다음 가리키는 것이 헤드가 가리키는 것과 같은지를 확인한다. -> 끝인지를 확인하는 방법 -> 같으면 false 반환
    // 원형이기 때문에 검사가 필요한데, non_exit인자는 원점을 지나쳐 원형으로 계속해서 순회하기 위해서 넣음.
    if ((non_exit == false&& (list->head == list->ci->next))
        return false;
 
    // 인덱스를 옮긴다.
    list->ci = list->ci->next;
 
    // 데이터 참조하여 값을 변경
    *data = list->ci->data;
 
    // true 반환
    return true;
}
 
int LPrevious(List* list, int* data, int non_exit)
{
    // LFirst 가 실행되지 않은 경우는 false
    if (list->ci == NULL)
        return false;
 
    // 해당 노드의 prev가 가리키는 것이 헤드가 가리키는 것과 같인지를 확인한다 -> 끝인지를 확인하는 방법 -> 같으면 false 반환
    if ((non_exit == false&& (list->head == list->ci->prev))
        return false;
 
    // 인덱스를 역행으로 옮긴다.
    list->ci = list->ci->prev;
 
    // 데이터 참조하여 값을 변경
    *data = list->ci->data;
 
    // true 반환
    return true;
}
 
void LRemove(List* list)
{
    // 타겟의 링크들이 끊기기 전에 따로 주소 값을 기록한다.
    struct node* target = list->ci;
 
    // 삭제할 타겟이 head가 가리키고 있는 노드라면
    if (list->head == target)
    {
        // 삭제할 타겟의 양 링크가 본인을 가리킨다면 삭제할 타겟은 하나!
        if (list->head == list->head->next) 
            list->head = NULL;
        else 
            list->head = target->next; // 헤드를 다음 노드로 옮겨준다.
    }
 
    // 타겟의 전 노드의 next 를 타겟의 next로 바꾼다
    target->prev->next = target->next;
 
    // 타겟의 다음 노드의 prev 를 타겟의 prev로 바꾼다
    target->next->prev = target->prev;
 
    // count를 줄인다.
    list->count--;
 
    // 메모리 해체는 NMM 객체가 알아서 해준다.
    return;
}
cs

728x90
반응형

+ Recent posts