반응형
728x90
반응형

그날 그날 정리하고 올려야 한다.. 묵혀두면 이렇게 고생한다.

-

오늘은 큐에 대해서 기록할 것이다. 큐는 굉장히 중요한 자료구조이다.

큐란 무엇인가?

비유해보자면 선착순이다. 먼저 온 사람이 먼저 서비스를 받는 것!

이를 두고 선입선출의 개념이라고 한다. 그림과 같이 나타낸다.

위 그림에서 옆으로 눕혀진 원통은 배열이라고 생각하면 된다. 배열에서 큐를 구현할 때를 생각해보자.

단순 배열 기반 큐에서의 삽입

단순 배열 기반의 큐에서 삽입 연산을 하는 것은 어려울 것이 없다. 배열의 끝에 값을 추가하듯이 인덱스를 증가시켜가면서 값을 넣으면 된다. 다음과 같이 말이다.

큐를 만들기 나름이지만, 인덱스를 증가시킨 후 삽입하는 것으로 하겠다.

단순 배열 기반 큐에서의 값의 꺼냄(삭제)

그러면 큐의 자료 구조 원칙에 맞도록 먼저 들어온 값을 꺼내려거든 어떻게 해야하나? 이런 경우는 삽입을 위한 인덱스를 하나 두고 증가시키면서 삽입을 진행하고, 꺼내려는 인덱스를 하나 두고 꺼내는 연산을 할 때마다 증가 시키면 된다!

삭제 또한 인덱스를 증가시킨 후 값을 삭제하는 것으로 하겠다.

큐의 꽉참/비워짐 여부 확인

큐를 가지고 삽입 / 삭제 연산을 진행하기 위해서 선행되어야 하는 연산은 큐가 비워졌는지 꽉 찾는지를 확인하면 된다. 아래의 그림은 큐의 비워진 상태를 나타대기 위해서 삽입은 멈추고, 꺼내는 연산만 한 그림이다. 

큐에 값이 꽉 찼음을 확인하는 방법은 선형 큐에서는 다음과 같다.

이 경우이다. 삽입 인덱스가 끝까지 가서 삽입 후 증가를 해야하는데 증가를 하려는데 증가가 안된다. 왜냐하면 인덱스가 끝에 왔기 때문이다. 이 경우 우리는 꽉 찼음을 확인한다. 

배열 기반 큐의 문제

위의 기록한 큐에는 문제가 있다. 다음과 같다.

이는 구조의 문제이다. -> 배열 길이가 정해져있고 순환하지 않는 선형이기 때문에 

이로 인해서 등장하는 것이 원형 큐이다!

원형 큐를 구현하면 선형 큐의 모든 개념 + 선형 큐의 문제 해결이 들어가기 때문에 선형 큐를 따로 포스팅하고 원형 큐에서 구현까지 마무리 지을 것이다.

728x90
반응형
728x90
반응형

SSH 에서 제공되는 로컬-원격 서버 간의 파일 송신 기능을 하는 명령이다 : SCP

어디서 어디로 보낼지에 따라 명령 형태가 달라지지만, 항목들은 비슷하다.

1] 원격 -> 로컬 파일 전송

원격 -> 로컬 파일 전송이라는 말의 의미는 "내 로컬에다가 원격에 있는 파일 수신 받을 꺼임." 이 소리임.

scp [옵션들] [계정명]@[원격 ip 주소]:[수신 받을 파일의 경로와 파일명] [수신할 로컬의 위치]

 

2] 로컬-> 원격 파일 전송 -> 이걸 주로 많이 쓰는 것 같다 나는

이건 그냥 "내 로컬에 있는 어떤 파일을 원격지로 보낼 꺼임." 이 소리임.

scp [옵션들] [전송할 파일의 경로 및 파일명] [계정명]@[원격 ip 주소]:[전송할 원격지의 경로]

3] [옵션들] ?

-P(대문자) : 포트 번호를 지정함
-p(소문자) : 원본 파일의 권한이나 시간 등을 유지한 채로 송/수신
-r : 하위 디렉토리 및 파일 포함.

728x90
반응형
728x90
반응형

Maria db를 사용하면서 DB를 저장한다거나, 복구 하는 방법에 대한 기록이다.

간혹 서버를 사용하다가 서버를 밀어야하는 상황이거나 다른 곳으로 옮겨야할 때, 사용한 DB의 규모가 소규모이지만 다시 하나 하나 database와 table 생성 및 데이터들을 insert하기에는 애매한 규모일 때, 나는 이 방법을 즐겨 쓴다.

 

mysqldump -u계정 -p비밀번호 --all-databases > /sql/형태로/저장될/경로/파일이름.sql

위와 같은 형태로 생성이 된다. ( sql )

일괄적으로 실행할 수 있는 쿼리 형태가 저장되어 있다.

그리고나서 생성된 .sql 쿼리 모음 back up 파일을 이용하여 다시 DB를 복원할 때는 다음과 같다.

mysql -u root -p DB이름< /경로/쿼리백업파일.sql

DB를 선택해서 복원한다. 즉, 원하는 DB는 따로 생성을 해야한다는 소리이다.

 

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
반응형
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 <iostream>
#include <vector>
 
using namespace std;
 
int main(void)
{
    // vector 값 10개를 추가한다.
    vector<int> vec;
    for (int i = 0; i < 10; i++)
        vec.push_back(i);
 
    // iterator를 이용한 순회
    vector<int>::iterator it;
    for (it = vec.begin(); it != vec.end(); it++)
        cout << *it << endl;
    cout << endl;
 
    // iterator를 포인터처럼 사용할 수 있다.
    it = vec.begin();
    cout << it[3<< endl// 3
    cout << *(it+3<< endl// 3
    cout << *(++it) << endl// 1
    cout << *it++ << endl// 1
    cout << *it << endl// 2
 
    it[0= 100;
    cout << it[0<< endl// 100
    
    *(it+1= 200;
    cout << *(it + 1<< endl// 200
    cout << it[1<< endl// 200
 
    cout << "--------------------------------------------------" << endl;
    vector<int>::const_iterator cit = vec.begin();
    for (cit = vec.begin(); cit != vec.end(); cit++)
        cout << *cit << endl;
    cout << "--------------------------------------------------" << endl;
    cout << cit[-1<< endl;
    cout << cit[-2<< endl;
    cout << cit[-3<< endl;
    cout << cit[-4<< endl;
    cout << cit[-5<< endl;
    cout << cit[-6<< endl;
    cout << cit[-7<< endl;
    cout << cit[-8<< endl;
    cout << cit[-9<< endl;
    cout << cit[-10<< endl;
    // cout << cit[-11] << endl; // 에러 ㅋ
    cout << "--------------------------------------------------" << endl;
    // vec.begin() 실제 요소, vec.end() 요소가 아님 -> 가리킬 수 없음
 
    // 값이 반전되어 출력함
    vector<int>::reverse_iterator rit;
    for (rit = vec.rbegin(); rit != vec.rend(); rit++)
        cout << *rit << endl;
 
    cout << "--------------------------------------------------" << endl;
    vector<int>::const_reverse_iterator crit;
    for (crit = vec.crbegin(); crit != vec.crend(); crit++)
        cout << *crit << endl;
 
    return 0;
}
cs
728x90
반응형

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

R-Value, Copy Elision, 이동 생성자, RVO, NRVO  (0) 2021.01.26
자료형 추론 auto / 범위 기반 for  (0) 2021.01.21
Priority Queue  (0) 2021.01.04
Queue  (0) 2021.01.04
Stack  (0) 2021.01.03
728x90
반응형

오늘도 역시 공부해놨던 부분을 이제 포스팅하는 시간이다. 역시 미리미리 해야한다.

스택이란?

일단 그림이 먼저다.

A, B, C 순서로 사각형을 넣을 것이다.

이렇게 된다. 그렇다면 다시 뺀다면?

이 때, 담는 용기, 통! 이것이 스택 자료구조이다. 
먼저 들어간 사각형 A가 제일 늦게 나오는 구조인 것이다. 이러한 개념을 후입선출이라고 한다. 나중에 들어간 것이 먼저 나오는 구조!

이를 구현하면 아래와 같다.

코드 및 실행 결과

---

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
 
#include <stdio.h>
#define STACK_LEN 10
 
#define TRUE 1
#define FALSE 0
 
struct array_stack
{
    int stack_arr[STACK_LEN];
    int top_index;
};
 
typedef array_stack stack;
 
void StackInit(stack* s);
int SIsEmpty(stack* s);
void SPush(stack* s, int data);
int SPop(stack* s);
int SPeek(stack* s);
 
int main(void)
{
    stack s;
    StackInit(&s);
 
    SPush(&s, 1);
    SPush(&s, 2);
    SPush(&s, 88);
    SPush(&s, 125);
    SPush(&s, 9912);
 
    while (!SIsEmpty(&s))
        printf("[%d] ", SPop(&s));
    fputs("\n", stdout);
 
    if (SIsEmpty(&s))
        printf("스택이 텅 비었습니다.\n");
    return 0;
}
 
void StackInit(stack* s)
{
    s->top_index = -1;
    return;
}
int SIsEmpty(stack* s)
{
    if (s->top_index == -1// 같아도 된다고?
        return TRUE;
    else
        return FALSE;
}
void SPush(stack* s, int data)
{
    if (s->top_index == STACK_LEN)
    {
        printf("스택이 꽉 찼습니다.\n");
        return;
    }
 
    s->stack_arr[++(s->top_index)] = data;
    printf("스택에 %d 값이 추가 되었습니다.\n", s->stack_arr[s->top_index]);
    return;
}
int SPop(stack* s)
{
    if (SIsEmpty(s))
    {
        printf("스택이 이미 비어져 있습니다.\n");
        return -1// 적절치 않다. -1 또한 값으로 넣을 수 있기 때문에. -> 차라리 프로그램을 종료시키는게 적절.
    }
    return s->stack_arr[s->top_index--];
}
int SPeek(stack* s)
{
    if (SIsEmpty(s))
    {
        printf("스택이 텅 비었습니다.\n");
        return -1;
    }
    return s->stack_arr[s->top_index];
}
cs

---

중간 중간에 보완해야할 부분이 있지만 개념 부분에서는 충분하기 때문에 보완하지는 않았다.

728x90
반응형

'프로그래밍응용 > C 자료구조' 카테고리의 다른 글

C Data Structure - 큐  (0) 2021.01.08
C Data Structure - 퀵 정렬  (0) 2021.01.06
C Data Structure - 병합 정렬  (0) 2021.01.05
C Data Structure - 힙 정렬  (0) 2021.01.05
C Data Structure - 연결 리스트  (0) 2021.01.04
728x90
반응형

이번에는 병합 정렬이다. 이게 생각보다 간단하긴 한데 복잡하다(?) 일단 시작한다.

병합 정렬이란?

병합 정렬의 기본은 다음과 같다.

1. 배열을 원소 단위로 쭉~~ 나눴다가
2. 다시 단계적으로 정렬과 동시에 결합을 반복한다.

그림으로 나타내자면 다음과 같다.

원소 단위 까지 쭉 쪼갠다.
정렬과 결합을 단계적으로 반복한다

그럼 문제는 무엇이냐? 어떻게 쪼갤 것인가? 이다. arr[0] , arr[1] 이게 쪼갠 것 아닌가? 아니다. 그냥 원소에 접근한거지 쪼갠게 아니다. 

그럼 쪼갠다 라는 의미를 아주 곰곰히 생각해봐야한다. 

쪼갠다는 것은 큰 덩어리부터 시작해서 작은 단위로 나눈다는 것이고, 내가 원소 레벨까지 쪼갰다 라는 의미는 내가 지금 쪼개고 있는 진행 흐름이 원소 단위까지 접근했다! 라는 것이다.

좀 어렵지만 잘 생각해라. arr[0] 에 접근하는 것은 우리 집 강아지와 할머니도 할 수 있는 행위이다.(집에 강아지와 할머니는 없다) 이것은 쪼갠 것이 아니고 접근한 거다. 

쪼갠다는 것은 그림의 설명과 같다.

제어할 수 있는 인덱스 범위를 한정지으면 되겠다 이것이다. 그런데 내가 가만히 보니 이 쪼개는 단계가 범위만 줄어드는 것 외에는 모두 같은 짓을 하고 있는 것이다. 그렇다면 원본 배열을 던지고, 그와 함께 범위 값만 다르게 던져주면 알아서 쪼개주는 함수가 없으려나?

있다. 바로 재귀 함수이다. 바로 코드를 본다

위 함수처럼 원본과 범위를 지정하여 함수에 던져주면 알아서 반으로 쪼갤 것이다. 

위 그림처럼 될 것이다.

그렇다면 쪼갰으니 정렬과 결합을 해야한다. 다시 MergeSort 코드를 보겠다.

MergeSort는 범위를 나누어 두 토막 내고(2개의 MergeSort) 그리고 정렬과 결합을(Merge) 수행한다 라는 사실을 기억해야하며, 신뢰해야한다. 그니까 밑에 잔뜩 실행된 MergeSort 함수는 신경쓰지말고, 현재 주어진 범위들에 대해서 정렬하고 결합한다는 것이다.

위 그림처럼 밑 바닥까지 MergeSort 하면서 내려가면 더 이상 쪼개지 못하므로 각 MergeSort에게 지정된 범위에 대해서 Merge 를 통해 정렬과 결합을 수행해주고 리턴한다 그러면 한 단계 위층에서의 MergeSort들은 내부적으로 정렬된 두 덩어리를 받을 것이고 또한 그들도 Merge 를 통해 정렬과 결합을 수행해주고 리턴해준다. 언제까지? 다 합쳐질 때까지!

물론!!!! 여기서 정렬은 조상님이 와서 대신 해주는게 아니다! Merge라는 함수 내에서 이를 구현해야한다. 이는 어렵지 않으므로 코드로 확인해보면 될 것이다.

이 코드가 핵심인데, 앞 범위 토막과 뒷 범위 토막의 요소들을 모두 둘둘씩 비교하여 작은 값을 새로운 공간에 차곡차곡 넣는 방식이다. 이게 가능한 이유는 각 토막들은 아래 계층에서 이미 정렬되어 올라왔기 때문이다! (쓰면서 명확히 깨달음 ㅋ)

위 비교 순서로 값이 정렬되고 합해지는데, 이 원리가 위 코드다 ;;;

내가 무슨 말을 하고 있는지 모르겠다면 직접해봐라 미래의 나야

코드 및 실행 결과

----

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
#include <stdio.h>
#include <stdlib.h>
 
void ShowArray(int arr[], int n)
{
    int i = 0;
    for (i = 0; i < n; i++)
        printf("%d ", arr[i]);
    fputc('\n', stdout);
    return;
}
void Merge(int arr[], int beginint mid, int end)
{
    int bidx = begin, eidx = mid + 1, cidx = 0, lidx = -1// begin side index, end side index, current index, last index
    int* temp = (int*)malloc((end - begin + 1* sizeof(int)); // 필요한 만큼만 할당한다.
 
    while (bidx <= mid && eidx <= end)
    {
        if (arr[bidx] <= arr[eidx])
            temp[cidx++= arr[bidx++];
        else
            temp[cidx++= arr[eidx++];
    }
    if (bidx > mid) // end side는 아직 다 안 채웠음.
        lidx = eidx;
    else if (eidx > end// begin side는 아직 다 안 채웠음.
        lidx = bidx;
 
    // 아직 정렬되지 않은 side를 채워넣는다.
    while (cidx < (end - begin + 1))
        temp[cidx++= arr[lidx++];
 
    // 원래 배열의 해당 인덱스 구간에 정렬된 결과를 반영한다.
    for (lidx = begin, cidx = 0; lidx <= end; lidx++, cidx++)
        arr[lidx] = temp[cidx];
 
    ShowArray(arr, 21);
    free(temp);
    return;
}
 
void MergeSort(int arr[], int beginint end)
{
    int mid = (begin + end/ 2;
 
    if (begin < end// 아직 나눌 수 있음.
    {
        MergeSort(arr, begin, mid);
        MergeSort(arr, mid + 1end);
 
        Merge(arr, begin, mid, end);
    }
    // 나눌 수 없는 경우는 원소 하나 단위이니 할 수 있는게 없다 -> 그냥 리턴
    return;
}
 
 
int main(void)
{
    int arr[21= { 20191817161514131211109876543210 };
    int i = 0;
 
    ShowArray(arr, 21);
    MergeSort(arr, 020);
    printf("최종 정렬 결과 : \n");
    ShowArray(arr, 21);
    return 0;
}
cs

----

머리가 참 아득해진다..

머리가 아득해지는 김에 빅오를 보자

병합 정렬 성능

최악/ 평균 / 최선에 상관없이 비교와 데이터 이동 연산을 모두 세어보면 O(n log 2 n)이다. 2는 아래 첨자 2이다.

728x90
반응형
728x90
반응형

오늘은 힙 정렬이다.

힙 정렬이란?

말 그대로 Heap 자료구조를 이용한 정렬 방식이다. Min Heap에서 루트 노드부터 빼오면 오름차순 정렬이 가능한 점을 이용해한 것이다.

힙 정렬은 Heap 자료 구조 조금 개조하면 만들 수 있다.
아래 그림과 같은 Min-Heap을 이용하면 정렬 성능이 죽여준다 ㅋ 

우선 순위 큐에서도 우선 순위가 높은 것을 작은 값이라고 약속하고 작성한다면 같은 결과이다. 뭐 그 기반이 heap 자료구조이니 heap을 통해서 구현했고, 이미 구현해놓은 것에 우선 순위 부분만 조금 수정하였다. 나중에 기억 안날 때에는 아래 링크 먼저 보고, 다음 코드를 확인하면 될 듯 하다. 

수정한 부분을 보자면, 원래 기존 heap 코드는 우선 순위를 결정하는 부분을 삽입마다 지정하기로 했는데 그것을 함수 포인터를 이용하여 없애버리고 자동으로 우선 순위를 나뉘도록 했다.

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

 

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

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

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
 
#include <stdio.h>
 
#define TRUE 1
#define FALSE 0
#define HEAP_LEN 100
 
struct heap_element // 정수 값을 저장하는 힙의 노드 구조체로 우선 순위를 지정할 수 있다.
{
    int data;
};
typedef struct heap_element heap_element;
 
struct heap
{
    int count;
    heap_element heap_arr[HEAP_LEN]; // 힙은 배열 구조를 기반으로 구현된다.
    int (*ComparePriority)(heap_element* h1, heap_element* h2);
};
typedef struct heap heap;
 
void HeapInit(heap* h, int (*CompFunc)(heap_element* h1, heap_element* h2));
int HIsEmpty(heap* h);
void HInsert(heap* h, int data);
int HDelete(heap* h);
void ShowHeap(heap* h);
 
int GetParentIndex(int child_index);
int GetLeftChildIndex(int parent_index);
int GetRightChildIndex(int parent_index);
 
int GetChildIndex(heap* h, int parent_index);
int ComparePriority(heap_element* n1, heap_element* n2);
 
void HeapSort(int arr[], int n, int (*CompFunc)(heap_element* h1, heap_element* h2))
{
    int i = 0;
    heap h;
    HeapInit(&h, CompFunc);
 
    for (i = 0; i < n; i++)
        HInsert(&h, arr[i]);
    ShowHeap(&h);
 
    printf("\n\n\n");
    for (i = 0; i < n; i++)
        arr[i] = HDelete(&h);
    ShowHeap(&h);
 
    return;
}
 
int main(void)
{
    int arr[21= { 20191817161514131211109876543210 };
    int i = 0;
 
    HeapSort(arr, sizeof(arr) / sizeof(int), ComparePriority);
 
    printf("\n\n\n");
    for (i = 0; i < 21; i++)
        printf("%d, ", arr[i]);
    fputc('\n', stdout);
    return 0;
}
 
int ComparePriority(heap_element* n1, heap_element* n2)
// 앞에꺼가 크면 양수, 뒤에꺼가 크면 음수
    return (n1->data - n2->data);
}
 
void HeapInit(heap* h, int (*CompFunc)(heap_element* h1, heap_element* h2))
{
    int i = 0;
    h->count = 0;
    for (i = 0; i < HEAP_LEN; i++)
        h->heap_arr[i].data = 0;
    h->ComparePriority = CompFunc;
    return;
}
int HIsEmpty(heap* h)
{
    return (h->count == 0) ? TRUE : FALSE;
}
 
void HInsert(heap* h, int data)
{
    int insert_index = h->count + 1;
    int parent_index = 0;
    heap_element new_element = { data };
 
    while (insert_index != 1// 처음 삽입되는 위치가 루트 노드가 아니면 혹은 갱신된 추가 인덱스가 루트 노드가 아니면
    {
        parent_index = GetParentIndex(insert_index);
        if (h->ComparePriority(&new_element, &(h->heap_arr[parent_index])) > 0// 우선 순위가 부모가 높다면 구조 재조정이 필요 없다.
            break;
        else // 추가할 노드가 우선 순위가 높은 경우, 계속 구조 재조정이 필요함.
        {
            h->heap_arr[insert_index] = h->heap_arr[parent_index];
            insert_index = parent_index;
        }
    }
    h->heap_arr[insert_index] = new_element;
    h->count++;
    return;
}
 
int HDelete(heap* h)
{
    int r_data = h->heap_arr[1].data; // 삭제할 데이터(Pop 할 데이터와 같은 의미)
    heap_element last_element = h->heap_arr[h->count]; // 비교 대상인 마지막 노드 지정( 마지막 노드를 루트 자리로 올려 비교하기 때문 )
    // 최종 결정된 인덱스
    int parent_index = 1// 루트 노드 부터 시작한다.
    int child_index = 0;
 
    while (child_index = GetChildIndex(h, parent_index))
    {
        if (h->ComparePriority(&last_element, &(h->heap_arr[child_index])) <= 0)// 자식 노드보다 우선 순위가 높다면 현재 구한 인덱스로의 변경만 하면 된다.
            break;
        else // 계속해서 구조의 재조정이 필요한 경우
        {
            h->heap_arr[parent_index] = h->heap_arr[child_index];
            parent_index = child_index;
        }
    }
    h->heap_arr[parent_index] = last_element;
    h->count--;
    return r_data;
}
// Heap의 내용을 트리의 계층(레벨) 별로 보여준다.
void ShowHeap(heap* h)
{
    int begin = 1end = 1, index = 1;
    int i = 0;
 
    if (h->count == 0)
    {
        printf("heap이 비었습니다!\n");
        return;
    }
 
    printf("%d, \n", h->heap_arr[1].data);
 
    while (index <= h->count)
    {
        begin = GetLeftChildIndex(begin), end = GetRightChildIndex(end); // 각 레벨 층의 시작과 끝 인덱스 설정 후 출력한다
        if (end > h->count)    end = h->count; // end가 마지막 노드보다 넓은 범위에 있다면 end를 마지막 노드의 인덱스로 설정
 
        index = end + 1// 위에서 지정한 end 다음 값으로 변경한다.
 
        for (i = begin; i <= end; i++)
            printf("%d, ", h->heap_arr[i].data);
        fputc('\n', stdout);
    }
    return;
}
 
// 부모 노드의 인덱스를 구한다.
int GetParentIndex(int child_index)
{
    return child_index / 2;
}
// 왼쪽 자식 노드의 인덱스를 구한다.
int GetLeftChildIndex(int parent_index)
{
    return parent_index * 2;
}
// 오른쪽 자식 노드의 인덱스를 구한다.
int GetRightChildIndex(int parent_index)
{
    return (parent_index * 2+ 1;
}
// 두 자식 노드 중 우선 순위에 따라 반환한다.
int GetChildIndex(heap* h, int parent_index)
{
    if (GetLeftChildIndex(parent_index) > h->count) // 자식 노드가 없으면 0 반환
        return 0;
    else if (GetLeftChildIndex(parent_index) == h->count) // 자식 노드가 하나인 경우에는 해당 인덱스 반환
        return GetLeftChildIndex(parent_index);
    else // 두 자식 노드 중 우선 순위가 높은 것의 인덱스 반환
    {
        int left_child_index = GetLeftChildIndex(parent_index), right_child_index = GetRightChildIndex(parent_index);
        if (h->ComparePriority(&(h->heap_arr[left_child_index]), &(h->heap_arr[right_child_index])) > 0)// 오른쪽 자식 노드가 더 우선 순위가 높다면
            return right_child_index; // 오른쪽 자식 노드의 인덱스 반환
        else // 왼쪽 자식 노드가 더 우선 순위가 높거나 양쪽 노드의 우선 순위가 같다면
            return left_child_index; // 왼쪽 자식 노드의 인덱스 반환
    }
}
 
cs

----

 

힙 정렬 성능

힙 데이터 저장 시간 복잡도 : O(log2 n) - 2는 아래 첨자의 2이다.
힙 데이터 삭제 시간 복잡도 : O(log2 n) - 2는 아래 첨자의 2이다.
힙 데이터 정렬의 경우, 저장과 삭제를 각각 n번씩 수행하므로 시간 복잡도 : O(n log 2 n) - 2는 아래 첨자의 2이다.

힙 구조에 데이터 삽입, 삭제 등이 이루어진다는 것을 고려한다고 하더라도, 앞서 봤던 O(n^2) 정렬들 보다도 성능이 좋은 것이다.

728x90
반응형
728x90
반응형

하 코딩해놓고 묵혀뒀다가 포스팅하느라 다시 보고 시간 버리는게 너무 아깝다 앞으로는 정말 열심히 바로바로 정리해서 올려야겠다.

그래도 다시 복습할 기회가 되었으니 의미있는 시간이라 생각하고 일반 연결 리스트에 대한 기록이다.

내가 (윤성우 교수님의 자료구조 책의 ADT를 보고) 코딩한 연결 리스트는 살짝 형태가 다르다. 그러므로 특징에 대해서 그림과 글의 적절한 특징 설명 이후 코드와 실행결과를 기록 하겠다.

일단,

단순 연결 리스트란?

구조체와 주소 값을 이용하여 현재 노드 내부에 다음 순서 노드의 주소 값이 저장되어 있는 형태로 이 이어진 연결을 통해 값을 순회할 수 있는 자료 구조이다.

노드 내부에 다음 순서 노드의 주소 값이 저장되어 있다는 것은 이러한 경우를 뜻한다.

구조

원래는 아래 그림과 같은 형식으로 리스트가 구성되어졌어야 했는데 

원래 이런 형식으로 초기 구성되어야 하는데

머리 회전이 덜 된 관계로 아래 처럼 다른 리스트를 만들어버렸다. 그건 뭐 그거대로 기록할 것임.

이렇게 해버렸음. 이걸로 설명을 적는다
노드를 추가한 형태

일단 위 구조로 생겨먹었고, 노드 구조체와 그 노드들을 담고 관리하는 자료구조 구조체가 있다

새 노드가 항상 헤드 다음 즉, 실질 데이터 노드 맨 앞에 항시 존재해야만 한다. 값을 추가할 때, 값과 링크 값만 변경하고 사용한다. 위와 같은 특성 때문에 리스트를 초기화할 때 다음 그림과 같이 꼭 -1 값으로 초기화 한다.

초기화 및 추가

값을 추가할 때에는 빈 노드의 값을 변경하여 사용하지만, 그래도 어찌됐던 빈 노드를 헤드 다음, 데이터 노드들 맨 앞에 두는 것이 원칙이기 때문에 데이터를 추가할 때 빈 노드 또한 추가해 놓아야 한다.

순회
다음은 순회이다.

삭제 
다음은 삭제이다. 내가 따로 만든 함수들이 있기 때문에 간단하게 설명한다.

 

코드 및 실행 결과

----

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
#include <stdio.h>
#include <stdlib.h>
 
struct ListNode {
    int data;
    struct ListNode* link;
};
 
struct ListManager
{
    struct ListNode* head;
    struct ListNode* ci; // current index; 
    struct ListNode* pi; // previous index;
 
    int node_count; // data 값이 유효한 node의 수
    int malloc_count; // 할당된 수
};
 
void ListInit(struct ListManager* lm);
void LInsert(struct ListManager* lm, int data);
int LFirst(struct ListManager* lm, int* data);
int LNext(struct ListManager* lm, int* data);
int LCount(struct ListManager* lm);
int LRemove(struct ListManager* lm);
 
void* CreateNodeMemory(struct ListManager* lm, int len);
void ShowList(struct ListManager* lm);
void DeleteNode(struct ListManager* lm, struct ListNode* target);
void DeleteList(struct ListManager* lm);
 
 
 
int main(void)
{
    struct ListManager lm;
    int data;
    ListInit(&lm);
 
    // 11, 11, 22, 22, 33 삽입
    LInsert(&lm, 11);
    LInsert(&lm, 11);
    LInsert(&lm, 22);
    LInsert(&lm, 22);
    LInsert(&lm, 33);
 
    printf("[11, 11, 22, 22, 33] 값 삽입 후 연결 리스트에 저장된 수 : (%d)\n", LCount(&lm));
    ShowList(&lm);
    fputc('\n', stdout);
 
    // 22, 22 삭제
    if (LFirst(&lm, &data))
    {
        if (data == 22)
            LRemove(&lm);
        while (LNext(&lm, &data))
        {
            if (data == 22)
                LRemove(&lm);
        }
    }
    printf("[22, 22] 값 삭제 후 연결 리스트에 저장된 수 : (%d)\n", LCount(&lm));
    ShowList(&lm);
    fputc('\n', stdout);
 
    // 나머지 11, 11, 33 삭제
    if (LFirst(&lm, &data))
    {
        if (data == 11 || data == 33)
            LRemove(&lm);
        while (LNext(&lm, &data))
        {
            if (data == 11 || data == 33)
                LRemove(&lm);
        }
    }
 
    printf("[11, 11, 33] 값 삭제 후 연결 리스트에 저장된 수 : (%d)\n", LCount(&lm));
    ShowList(&lm);
    fputc('\n', stdout);
 
    // 원래대로 삽입 복원
    LInsert(&lm, 11);
    LInsert(&lm, 11);
    LInsert(&lm, 22);
    LInsert(&lm, 22);
    LInsert(&lm, 33);
 
    printf("원래 [11, 11, 22, 22, 33] 값 삽입 후 연결 리스트에 저장된 수 : (%d)\n", LCount(&lm));
    ShowList(&lm);
    fputc('\n', stdout);
 
    printf("완전 리스트 자체 삭제 이후 : (%d)\n", LCount(&lm));
    DeleteList(&lm);
    ShowList(&lm);
 
    printf("\n\n");
    return 0;
}
 
 
void ListInit(struct ListManager* lm)
{
    // count 초기화
    lm->node_count = 0;
    lm->malloc_count = 0;
 
    // head 다음으로 항상 유지하는 빈 노드 생성
    struct ListNode* new_node = (struct ListNode*)CreateNodeMemory(lm, sizeof(struct ListNode));
    new_node->data = -1;
    new_node->link = NULL// 제일 끝이므로 무조건 NULL을 갖는다.
 
    // 연결리스트 헤드 초기화
    lm->head = (struct ListNode*)CreateNodeMemory(lm, sizeof(struct ListNode));
    lm->head->data = -1;
    lm->head->link = new_node;
 
    // 인덱스 초기화
    lm->ci = NULL;
    lm->pi = NULL;
 
    return;
}
 
void LInsert(struct ListManager* lm, int data)
{
    struct ListNode* new_node = (struct ListNode*)CreateNodeMemory(lm, sizeof(struct ListNode));
 
    // head 다음의 빈 노드에 값을 반영, 링크는 건들지 않는다.
    lm->head->link->data = data;
 
    // empty_node 설정
    new_node->data = -1;
    new_node->link = lm->head->link;
    lm->head->link = new_node;
 
    lm->node_count++;
    return;
}
 
int LFirst(struct ListManager* lm, int* data)
{
    if (lm->node_count == 0)
    {
        printf("순회할 노드가 없습니다.\n");
        return false;
    }
    lm->ci = lm->head->link->link;
    lm->pi = lm->head->link;
 
    *data = lm->ci->data;
    return true;
}
 
int LNext(struct ListManager* lm, int* data)
{
    if (lm->ci->link == NULL)
        return false;
 
    lm->pi = lm->ci;
    lm->ci = lm->ci->link;
    *data = lm->ci->data;
    return true;
}
 
int LCount(struct ListManager* lm)
{
    return lm->node_count;
}
 
int LRemove(struct ListManager* lm)
{
    int remove_value = lm->ci->data;
    struct ListNode* rtarget = lm->ci;
 
    lm->pi->link = rtarget->link;
    lm->ci = lm->pi;
 
    DeleteNode(lm, rtarget);
    return remove_value;
}
 
void* CreateNodeMemory(struct ListManager* lm, int len)
{
    lm->malloc_count++;
    return (void*)malloc(len);
}
 
void ShowList(struct ListManager* lm)
{
    struct ListNode* index_node = NULL;
 
    if (lm->malloc_count == 0)
    {
        printf("head와 new node 및 일반 node들까지 모두 존재하지 않습니다.");
        return;
    }
    else if (lm->node_count == 0)
        printf("추가된 노드는 모두 제거된 상태이지만, head와 new node가 존재하고 추가 가능한 상태입니다.\n");
 
    for (index_node = lm->head; index_node != NULL; index_node = index_node->link)
    {
        printf("[%d|%p]", index_node->data, index_node);
        if (index_node->link != NULL)
            printf("->");
    }
    fputc('\n', stdout);
    return;
}
 
void DeleteNode(struct ListManager* lm, struct ListNode* target)
{
    if (target->data != -1)
        lm->node_count--;
 
    free(target);
    lm->malloc_count--;
 
    return;
}
 
void DeleteList(struct ListManager* lm)
{
    struct ListNode* index_node = NULL;
    struct ListNode* next_node = NULL;
    for (index_node = lm->head; index_node != NULL; index_node = next_node)
    {
        next_node = index_node->link;
        DeleteNode(lm, index_node);
    }
    if (lm->malloc_count != 0)
        printf("메모리 해체에 문제가 있습니다\n");
    return;
}
 
 
cs

----

메모리 관리가 중요한 것 같다.ㅋ

728x90
반응형

'프로그래밍응용 > C 자료구조' 카테고리의 다른 글

C Data Structure - 병합 정렬  (0) 2021.01.05
C Data Structure - 힙 정렬  (0) 2021.01.05
C Data Structure - 삽입 정렬  (0) 2021.01.04
C Data Structure - 선택 정렬  (0) 2021.01.04
C Data Structure - 버블 정렬  (0) 2021.01.03
728x90
반응형

 

우선 순위 큐란?

Heap 자료 구조를 내부적으로 사용한다.
기본적으로 값이 클수록(문자의 경우 사전 상 뒤의 순서일수록) 우선순위가 높다. <- 이는 우선 순위를 정하기 나름이다.

이렇게 나올줄 알고 이미 c언어로 구현을 해보았다ㅋ 매우

https://typingdog.tistory.com/110

 

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

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

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
#include <queue>
#include <iostream>
#include <string>
 
using namespace std;
 
int main(void)
{
    priority_queue<string> pq;
    string words[3= {
        "Apple",
        "Banana",
        "Corn"
    };
    for (int i = 0; i < 3; i++)
        pq.push(words[i]);
    while (!pq.empty())
    {
        cout << pq.top() << endl;
        pq.pop(); // 분명히 top 에서 Pop 으로 뽑아내지만 선입 선출의 순서가 아닌 우선 순위대로 값을 뽑아낸다. 
        // 내부적으로 사용되는 자료 구조 방식이 다르기 때문에.
    }
 
    return 0;
}
cs

----

 

728x90
반응형

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

자료형 추론 auto / 범위 기반 for  (0) 2021.01.21
Iterator 사용 방법 예시  (0) 2021.01.05
Queue  (0) 2021.01.04
Stack  (0) 2021.01.03
Tuple  (0) 2021.01.03

+ Recent posts