반응형
728x90
반응형

미루고 미뤄왔던 보간 탐색에 대한 포스팅이다!!!

 

C Data Structure - 이진 탐색

이진 탐색을 구현해보았다. 이진 탐색에 대해 간단하게 기록하기 전에 이진 탐색의 전제 조건에 대해서 기록을 먼저 해보겠다. 이진 탐색의 전제 조건 이진 탐색은 오름차순이든 내림차순이든

typingdog.tistory.com

위에는 보간 탐색의 기반이 될 탐색의 원리를 설명해 놓은 글이다. 무려, 4개월 ~ 5개월 전에 내가 작성했던 글이다. 지금 봐도 상당히 디테일하게 잘 작성한 것 같은데 , 코드라던가 실행 결과 등을 제대로 올리지는 않았더라.

그래서 이번 포스팅에서 보간 탐색과 함께 이진 탐색의 재귀적 구현, 이진 탐색의 반복문 구현까지 원쁘라스투(1+2) 형태로 포스팅 들어간다.

먼저, 보간 탐색이 Main 이니 보간 탐색에 대해 살펴보도록 한다.

보간 탐색이란?

이진 탐색을 살짝 더 효율적으로 개선한 것이다! 기존의 이진 탐색의 경우 다음과 같이 탐색이 진행되는데,

3번의 이진 탐색이 진행되었고, 그 3번의 이진 탐색 안에는 수 많은 계산들과... 수 많은 조건 검사 등으로 인해 그냥 선형 순차 탐색만도 못한 효율이 나와버렸다. 그 이진 탐색 시켜주려고 정렬까지 한 것을 생각해보면 억울하다. 

물론.. 미미한 성능 차이이고, 데이터가 더 많고, 타겟이 더 안쪽에 있었을 때에는 분명히 이진탐색이 개쩔긴하다ㅋㅋ 예를 들기 위해서 극단적으로 말해본 것이다.

왜 위와 같이 선형 순차 탐색보다도 못한 결과가 일어났을까? 왜냐하면...

첫 번째, 데이터가 생각보다 앞에 있었기 때문이다.
두 번째, 죽으나 사나 수행하는 반 절 나누기로 인해서 절반 지점부터 시작하는 탐색 시작 위치가 문제이다.

그래서 데이터가 극 초반이나, 극 후반에 존재할 경우에도 계속 반절부터 시작할꺼냐! 라는 생각에서부터 시작되었다.

첫 번째 원인은 타겟이 치우져 있는 것은 우리가 어떻게 할 수 없는 것이다. 사용자가 찾겠다고 하는 타겟이 거기 있는걸 무슨 수로 해결하는가?

그렇다면 두 번째, 앞으로 절반 지점부터 시작하지 않으면 되지않을까? 

그래서 이렇게 바꿔봤다. 이 공식은 아마도 수학 센세들이 힘들게 연구한 공식이 아닐까 한다. 저 공식은 

"내가 찾는 값과 그 값이 저장되어 있는 위치는 비례하기 때문에 전체 길이에서
대충 이 정도 어디쯤에느으으은~~ 있지 않겠는가?"

를 이야기하는 공식이다. 그럴 수 밖에 없는게 이진 탐색과 마찬가지로 정렬되어 있는 데이터 세트를 대상으로 하기 때문에 당연히 전체 길이에서 찾으려는 값은 어림잡아 이쯤에 있겠거니 하고 추측해볼 수 있는 것이지 않겠는가?

그래서 그걸 실제로 구현해 본 예이다.

그런데 다음과 같은 예에서 탈출 조건에 문제가 있다. 

위의 조건대로 데이터 세트와 타겟을 2로 조정하여 실행해보면 실행이 무한 루프에 빠진다. 즉, 탈출 해야하는데 탈출 조건을 충족시키지 못해서 무한 루프에 빠지는 것이다.

그렇다면 왜 이전 탈출 조건인 start <= end 조건은 먹히지 않는걸까? 그 이유를 아래에 정리해 보았다.

번호 순서대로 읽으면 된다.

그도 그럴 것이, 새로 정의된 공식으로 인해서 정해지는 index 값은 "너가 찾는 타겟 값은 전체 길이 중에 대충 어디어디 쯤에 있어~" 라고 이야기해주는 것과 같다. 그렇다보니, 아무리 start 와 end 가 갱신된다고 하더라도 이전과 비슷한 index 값을 계산해서 내놓을 확률이 높다. 더군다나 start가 1밖에 증가하지 않은(범위가 겨우 인덱스 하나 줄어든) 상황에서는 더더욱 그럴 확률이 높다. 이런 와중에 갱신된 범위가 target이 포함되지 않는 상황까지 되어버린 것이다. 그러면 아주 그냥 삼위일체이다.

위의 예시 상황으로 보자면 다음과 같은 입장들이 되는 것이다.

새 공식 曰 : target 2는 인덱스 0 쯤에 있어~, index = 0 ~
start, end 曰 : 오 마침 index가 0이고 index+1이 start로 넘어오니 결국, 범위가 하나 밖에 안 줄었네?
arr[start], arr[end] 曰 : 3 ~ 9 값이군.
target 曰 : 난 2인데!!!! 왜 3 ~ 9를 보고 있냐!!! 다음 재귀 호출 때 탈출해라!!!
                   [ 재귀 호출 ]
탈출 조건 曰 : 응~ start <= end 조건 성립해서 탈출 안 해 ~
새 공식 曰 : (범위가 하나 줄은 것 가지고는 결과는 그대로겠네..?) 음.. 뭐.. target 2는 인덱스 0쯤에 있어~, index = 0 ~
                   [... 무한 반복 ...]
컴파일러 曰 : '신발장 크레파스 놈들이!!!!!!!!!!!!!!!!!!!!! 꽥(무한 루프 터.짐.)'

위의 대화가 탈출 조건과 새 공식의 순서가 코드와 조금 다르긴 하지만 문제는 없다. 결국은 같기 때문이다.

뭐 위 대화까지 보고도 이해가 안된다면 내 잘못이다. 설명을 못한 것이다. 근데 이게 글로만 설명하기 겁나 애매하고 짜증난다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋ

아래는 코드와 실행결과이다. 탈출 조건까지 최종 수정된 보간 탐색과 이진 탐색의 재귀적 구현과 반복적 구현까지 모두 넣어 놓은 3종 세트인 것이다.

코드와 실행 결과

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <stdio.h>
 
// 이진 탐색 반복적 구현
int BinarySearch(int arr[], int len, int target)
{
    int begin = 0end = len - 1;
    int mid = -1;
 
    while (begin <= end)
    {
        mid = (begin + end/ 2;
        printf("반복! [mid = %d]\n", mid);
 
        if (arr[mid] == target)
            return mid;
        else
        {
            if (target > arr[mid])
                begin = mid + 1;
            else
                end = mid - 1;
        }
    }
    return -1;
}
 
// 이진 탐색 재귀적 구현
int BinarySearchRecursive(int* arr, int beginint endint target)
{
    int mid = (begin + end/ 2;
    printf("재귀 함수 호출! [mid = %d]\n", mid);
 
    if (begin <= end)
    {
        if (target == arr[mid])
            return mid;
        else if (target > arr[mid])
            return BinarySearchRecursive(arr, mid + 1end, target);
        else if (target < arr[mid])
            return BinarySearchRecursive(arr, begin, mid - 1, target);
    }
    else
        return -1;
}
 
// 보간 탐색 재귀적 구현
int InterpolationSearchRecursive(int* arr, int start, int endint target)
{
    int index = ((double)(target - arr[start]) / (arr[end- arr[start]) * (end - start)) + start;
    printf("보간 재귀 함수 호출! [index = %d]\n", index);
 
    if (arr[start] <= target && arr[end>= target) // if (start <= end)
    {
        if (target == arr[index])
            return index;
        else if (target > arr[index])
            return InterpolationSearchRecursive(arr, index + 1end, target);
        else if (target < arr[index])
            return InterpolationSearchRecursive(arr, start, index - 1, target);
    }
    else
        return -1;
}
 
int main(void)
{
    int arr[] = { 13579 };
    int target = 2;
    int result = -1;
 
    // 보간 탐색 재귀적 구현 호출
    printf("\n\n보간 탐색 재귀적 구현 호출 - - - - - - - - - - - - \n");
    result = InterpolationSearchRecursive(arr, 0sizeof(arr) / sizeof(int- 1, target);
    if (result != -1)
    {
        printf("[interpolation recursive] [%d는 arr배열의 %d 번째 위치에 존재합니다\n", target, result);
    }
    else
    {
        printf("[interpolation recursive] %d는 arr 배열에 존재하지 않습니다.\n", target);
    }
    
    // 이진 탐색 재귀적 구현 호출
    printf("\n\n이진 탐색 재귀적 구현 호출 - - - - - - - - - - - - \n");
    result = BinarySearchRecursive(arr, 0sizeof(arr) / sizeof(int- 1, target);
    if (result != -1)
    {
        printf("[recursive] [%d는 arr배열의 %d 번째 위치에 존재합니다\n", target, result);
    }
    else
    {
        printf("[recursive] %d는 arr 배열에 존재하지 않습니다.\n", target);
    }
 
    // 이진 탐색 반복적 구현 호출
    printf("\n\n이진 탐색 반복적 구현 호출 - - - - - - - - - - - - \n");
    result = BinarySearch(arr, sizeof(arr) / sizeof(int), target);
    if (result != -1)
    {
        printf("[iteration] %d는 arr배열의 %d 번째 위치에 존재합니다\n", target, result);
    }
    else
    {
        printf("[iteration] %d는 arr 배열에 존재하지 않습니다.\n", target);
    }
 
    return 0;
}
cs

없는 값으로 인해 탐색 실패 결과

 

탐색 성공

 

728x90
반응형
728x90
반응형

기수 정렬이다. 드디어 정리할 시간이 다가왔다.

Q. 기수 정렬에서 기수란 무엇인가?

A. 숫자 표현(진법체계)에 기준이 되는 수를 뜻한다. 예를 들자면 16진, 10진, 8진, 2진 등을 기수라고 한다.

Q. 그럼 기수 정렬은 무엇인가? 기수를 통해서 어떻게 정렬을 이뤄내는가?

A. 예제를 통해서 보겠다.

정렬이 되는 대상들을 하나의 진법 속 요소들로 가정하고,

해당 진법 배열에 값을 모두 넣은 후, 단순히 순서대로 꺼내기만 한다.

이런 식으로 동일한 기수의 요소들끼리 해당 기수의 버킷을 이용하여 정렬이 이루어지는 것이 기수 정렬이다.

알파벳을 정렬할 때
여러 자리도 정렬이 가능하다.

중요한 것은 해당 기수의 버킷에 넣고 다시 버킷의 처음부터 순서대로 꺼낼 뿐이다. 그러니까 값의 비교가 이루어지지 않는다는 것이다! 그렇기 때문에 정렬할 대상들에 따라 반복문을 여러 번 사용해야할 수도 있고, 중간에 갑자기 진행을 끊을 수도 있으며, 정렬할 수 있는 대상이 제한이 되기도 한다.

예를 들어, 1, -5, 100 이런 요소들에 대해서는 정렬이 불가능하다. - 를 기수 버킷으로 어떻게 표현할 것이란 말인가?

다음으로 간단한 숫자 정렬하는 예제를 보겠다.

코드 및 실행 결과

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
#include <stdio.h>
 
int main(void)
{
    int radix_bucket[10]; // 0 ~ 9 까지 
    int data[] = { 5172490 };
    int data_len = sizeof(data) / sizeof(int);
 
    int i = 0, j = 0;
 
    // 기수 버킷 초기화
    for (i = 0; i < 10; i++)
        radix_bucket[i] = -1;
 
    // 정렬 되지 않은 데이터 출력
    for (i = 0; i < data_len; i++)
        printf("%d ", data[i]);
    fputc('\n', stdout);
 
    // 기수 버킷 이용
    for (i = 0; i < data_len; i++)
        radix_bucket[data[i]] = data[i];
 
    // 기수 버킷에서 값을 옮김
    for (i = 0, j = 0; i < 10; i++)
        if (radix_bucket[i]!=-1)
            data[j++= radix_bucket[i];
 
    // 정렬된 데이터 출력
    for (i = 0; i < data_len; i++)
        printf("%d ", data[i]);
    fputc('\n', stdout);
 
    return 0;
}
cs

 정렬 전 / 기수 정렬 후

 

728x90
반응형
728x90
반응형
 

C Data Structure - 테이블

자 오늘은 테이블과 해시이다. 자료구조는 자료를 저장하는 구조를 나타내는 것이지만, 그 구조 속에 들어 있는 값을 찾아서 꺼내는 것 또한 자료 구조의 영역 범위 내이다. 그래서 배열 리스트,

typingdog.tistory.com

지난 이야기..

문제점

뭐 위와 같은 문제들이 있다. 위의 문제의 전제는 등록되는 수는 100명도 안되고, 거의 그대로지만 번호만 오지게 큰 스케일로 변환되는 경우이다. 

해결 아이디어

그러면 어떻게 하면 될까? 

큰 인덱스 키 값의 크기를 줄이면 된다!

<인덱스를 축소 변환하는 과정>

1. 어차피 등록되는 인원은 100명이 안될 것이기 때문에 20171300 ~ 20171399 라고 범위를 어림 잡는다 
2. 그러면 말이 20171300 부터 20171399까지 이지 결국엔 0부터 99까지라고 봐도 문제가 없다.
3. 그렇다면 인덱스를 대입하기 전에 20171300이 오면 무조건 0으로 변환해주고, 20171344가 오면 무조건 44로 변환해주며, 20171399가 오면 무조건 99로 변환해주어 인덱스에 대입되도록 하면 어떨까?
4. 등록번호 % 100 을 한 결과라면 무조건적으로 위의 결과를 얻는다.
5. 그렇다면 user acc[100] 을 그대로 넣고 acc[uid%100] 으로 접근하면 배열의 크기를 비정상적으로 늘리는 고민을 해볼 필요도 없게되는 것이다.

이러한 과정에서 축소 변환해주는 역할을 하는 것을 해쉬(hash)라고 하며, 해당 역할을 하는 함수를 해쉬 함수라고 한다.

해쉬 함수는 다음과 같다. 겁나 간단하다.

이러한 해쉬 함수는 위에서 밑줄 친 것처럼 다양한 값의 형태에 따라 달라진다. 해쉬되어야 할 키 값의 상황? 문맥? 에 따라서 적용되는 해쉬 함수의 내용이 달라지고, 정답이 없다는 뜻이다.

적용된 코드 분석

등록을 할 때에 hash 함수를 이용하며,

값을 꺼낼 때에도 hash 함수를 이용하여 인덱스를 축소 변환하여 인덱싱한다.

전체 코드 및 실행 결과

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>
#include <string.h>
 
#pragma warning(disable:4996)
 
struct user
{
    int useruid;
    char username[20];
    int age;
};
 
typedef struct user user;
 
 
int HashFunction(int useruid)
{
    return (useruid % 100);
}
 
void RegisterUser(user* u, int useruid, char* username, int age)
{
    int hashed_uid = HashFunction(useruid);
    u[hashed_uid].useruid = useruid;
    strcpy(u[hashed_uid].username, username);
    u[hashed_uid].age = age;
    return;
}
 
int main(void)
{
    user acc[100];
    user search;
 
    int uid = 0, age = 0;
    char name[20= { 0, };
 
    // 유저 정보 입력
    printf("유저의 등록 번호를 입력하세요(20171300 ~ 20171399) : ");
    scanf("%d"&uid);
    printf("유저의 이름을 입력하세요 : ");
    scanf("%s", name);
    printf("유저의 나이를 입력하세요 : ");
    scanf("%d"&age);
 
    // 유저 등록
    RegisterUser(acc, uid, name, age);
 
    // 유저 조회
    printf("찾고자 하는 유저의 등록 번호를 입력하세요(20171300 ~ 20171399) : ");
    scanf("%d"&uid);
 
    // 등록 번호를 통해 한 번에 검색 -> 키를 인덱스로 바로 쓰기 때문이다. 계산만 한 뒤 접근하면 끝ㄷㄷ
    search = acc[HashFunction(uid)]; // 값이 구조체 구조 그대로 복사됨(얕은 복사)
 
    // 조회 결과 출력
    printf("조회 결과 --- \n");
    printf("유저 등록 번호 : %d \n", search.useruid);
    printf("유저 이름 : %s \n", search.username);
    printf("유저 나이 : %d \n", search.age);
 
 
    return 0;
}
cs

 

그런데 또 문제점

짜증나게 또 문제가 발생하는 것이다.. 이번엔 등록해야하는 수가 갑자기 늘어나 버려서 다음과 같은 현상이 발생하는 것이다. 

1. 한 200명~300명 정도 난다.
2. 그래서 배열의 길이를 넉넉하게 user acc[400]; 정도로 주었다.
-> 이 방법 또한 썩 마음에 들지 않는다. 왜냐하면 등록 수가 늘어날때마다 계속 변경을 해줘야 하기 때문이다.
3. 그런데 문제는 여기서부터이다. 2017135020171450의 해쉬 결과는 50으로 같다.
-> 왜냐하면 100으로 나눈 나머지이기 때문에!(직접 나눠보라)
4. 20171350 등록 번호인 사람과 20171450 등록번호인 사람의 해쉬 결과가 50으로 같으므로 둘다 acc[50]에 정보가 등록될 것이다.. 그러니까 먼저 저장되는 값은 손실 된다는 의미이다. 원래 기대 결과는 20171350은 [50]에 20171450은 [150] 에 저장되는 것을 기대했는데 말이다.

지금 두 가지 문제가 발생하였다.

첫 번째는 문제는 등록되는 수가 넘쳐날 때마다 테이블의 크기를 갱신해야하는 상황이 발생한다.

두 번째는 문제는 분명히 다른 등록번호임에도 불구하고, 해쉬된 결과로 같은 인덱싱 번호가 나왔다. 

먼저, 두 번째 문제와 같이 같은 해쉬 결과가 나오는 것을 충돌(Collision)이라고 한다. 이러한 충돌을 막으려면 어떻게 해야할까? 해쉬 함수를 기가 막히게 잘 만들어야 한다. 해쉬 함수는 해쉬하려고 넘어오는 키 값의 범위나 종류나 형태, 양상에 따라 달라질 수 있기 때문에 매우 주관적이며, 답이 없다. 

A회사에서 만든 해쉬 함수가 기가 막히게 성능이 잘 나오다가도 B회사에서 A회사의 해쉬 함수를 쓰면 동작이 잘 안될 수도 있다. 그렇지만 최대한 공통으로 적용할 수 있는 여러 알고리즘? 들이 존재한다.

자릿수 선택, 폴딩 방법, 선형 조사법, 이차 조사법, 이중 해쉬, 체이닝 ... 등

이렇게 여러 방법들이 존재하지만 이 방법들의 공통점은 해쉬 해야할 키 값을 어떻게 변환해야 충돌이 덜 일어나는가에서부터 시작하는 방법들이다.

가장 좋은 해쉬 방법을 만드는 것은 키 값의 부분을 활용하여 해쉬된 키 값을 만들 것이 아니라, 키 값의 전체를 활용하여 해쉬된 키 값을 만들어야 한다는 것이다.

위의 예제에서도 키 값의 마지막 두 자리 수만 가지고 장난질 했기 때문에 문제가 되었다. 키 전체를 활용할수록 콜리전이 발생할 확률이 매우 낮아진다.

첫 번째 문제는 해쉬 함수의 구현에 따라 달라진다. 해쉬 함수를 통해 변환되는 해쉬된 키 값의 범위를 어떻게 정하느냐에 따라 적절한 할당 크기를 정할 수 있기 때문이다.

테이블과 해쉬 등은 성능은 좋지만 이러한 충돌(collision) 문제로 인해, 그리고 키를 이용하여 제어해야하는 곳에서만 주로 사용해야하기 때문에 사용되는 곳이 제한된다. 

테이블과 해쉬를 살펴봤는데 해쉬 테이블은 기본 동작 원리나 문제점 그리고 적용 가능한 작업의 특징 정도로만 공부하고 넘어가는게 좋을 것 같다.

728x90
반응형
728x90
반응형
 

C Data Structure - 그래프란?

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

typingdog.tistory.com

 

 

C Data Structure - 그래프의 기본

드디어 그래프의 기본 코드이다. 이전 포스팅에서 설명했듯이, 그래프를 표현하는 방법 중 인접 리스트 방법을 이용하여 기본적인 그래프를 구현해 볼 것이다. 그 인접 리스트에 해당하는 방법

typingdog.tistory.com

일단, 위의 그래프 기본들을 기반으로 작성되기 때문에.. 링크해둔다.

뭐 BFS니 DFS니 이상한 용어 써가면서 아는 척 오졌던 학과 동기들 보면 이제 이렇게 생각하라. 

대단한 친구들이네

근데 사실, 이상한 용어가 나와서 오히려 겁 먹고 어려울지 모르겠지만 조금만 이해하면 그렇게 어렵지 않다. 

BFS 나 DFS 모두 Search의 일종이다. 뭐 말로만 Search지 순회다 순회. 순회하면서 원하는 값이 나오면 조건문으로 캐치하면 그게 Search이지 무엇이겠는가?

아무튼 두 개념 모두 순회인데, 순회하는 방법이 다르다. 그래서 두 가지를 모두 동시에 기록해 두려고 한다.

DFS (Depth First Search)

DFS는 Depth First Search 로 깊이 우선 탐색이다. 말 그대로 우선적으로 그래프를 깊게 깊게 파고 들어간다는 것이다. 그렇게 전부 파고 들어 갔다면 다시 되돌아 나오면서 깊게 파면서 놓쳤던 주변을 살펴보며 나오는 형식이다.

위 그림과 같은 식인데, 윤성우 교수님의 자료구조 책에서는 다음과 같이 표현한다. "너니까 이야기해주는거야" 하면서 깊고 친한 관계라고 생각하는 친구들에게만 비밀 이야기를 전달한다. 그러다보니 3번과 같은 정점에게도 퍼져있는 비밀 이야기가 소문이 나는 상황에 비유를 하셨다ㅋㅋ

중요한 것은 돌아 나오는 것각 정점들의 방문 여부이다. 이 둘을 어떻게 관리하고 코드로써 표현할 것인지가 큰 쟁점이다.

첫째로 돌아 나온 다는 것은 그 발자취를 기록하면 된다. 기록할 때 그냥 순서대로 기록하고 기록한 순서대로 다시 보는게 아니라 돌아나가는 것은 역재생하는 것과 같으므로 스택 자료구조를 이용한다.

둘째로 정점들의 방문 여부는 인덱스 값과 정점 값을 매칭 시킨 flag 역할을 1차원 배열로 처리를 하면 된다.

참고>>

이런 경로 또한 가능하다. 구현해 보면 알겠지만 하나의 정점에 연결된 정점들 중 어떤 것을 선택하는지에 대한 로직은 순회 로직에 들어가지 않는다. 그러니까 시작 정점에서 왼쪽 정점으로 갈지, 오른쪽 정점으로 갈지를 중요시 하지 않는다는 소리이다. 이는 정점간 연결을 나타내는 자료구조 내에서 어떤 순서로 연결 정보를 저장했는지에 따라서 다르게 나온다. 그니까 어떤 경로든 해당 탐색의 기본 원리에만 들어 맞다면 경로 순서의 맞고 틀리고를 따지는건 의미가 없다는 소리이다.

BFS (Breadth First Search)

반면에 BFS는 Breadth First Search 로 너비 우선 탐색이다. 말 그대로 우선적으로 넓게 넓게 퍼뜨리며 그래프를 파고들겠다는 것이다. 그러니까 어떤 정점을 순회하기만 했다 하면 바로 그냥 그 해당 정점에 연결된 모든 정점에게 방문하는 것이다. 

이건 약간 예로 들자면, 정책 발표에 따른 메시지 퍼짐이다! 출발 지점에서 정책을 발표한다. 그 양 옆 정점은 그 해당 정책을 티비를 통해 실시간으로 들은 정점들이고, 그리고 나서 그 실시간으로 들은 정점들이 지인을 만날 때마다 그 정책에 대해서 이야기하는 모습 같다고 비유할 수 있다.

중요한 것은 방문/순회 차례에 대한 기록각 정점들의 방문 여부이다. 이 둘을 어떻게 관리하고 코드로써 표현할 것인지가 큰 쟁점이다. 

첫째로 물론 각 정점들의 방문 여부는 위에서와 마찬가지로 flag 역할의 1차원 배열로 충분하다. 

둘째로 방문/순회 차례에 대한 기록은 위 DFS와는 조금 다르다. 방문한 정점과 연결된 정점들을 모두 방문하고, 그 다음 순서까지 정한 뒤 넘어가야헌다 그것이 BFS에서의 순회라고 보면 된다. 그러기 위해서는 꼭 순서를 기록해야한다. 그렇지 않으면 순회에 비효율이 생길 수 있다. 이 때 사용하는 것이 이다

이렇게 두 개념을 코드를 통해 구현해 볼 것이다 

728x90
반응형
728x90
반응형

드디어 그래프의 기본 코드이다.

이전 포스팅에서 설명했듯이, 그래프를 표현하는 방법 중 인접 리스트 방법을 이용하여 기본적인 그래프를 구현해 볼 것이다. 그 인접 리스트에 해당하는 방법 또한 이미 이전에 포스팅 한 코드를 기반으로 만들 것이다. 해당 리스트 코드는 따로 첨부하도록 할 것이다.

 

C Data Structure - 그래프란?

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

typingdog.tistory.com

 

 

C Data Structure - 연결 리스트

하 코딩해놓고 묵혀뒀다가 포스팅하느라 다시 보고 시간 버리는게 너무 아깝다 앞으로는 정말 열심히 바로바로 정리해서 올려야겠다. 그래도 다시 복습할 기회가 되었으니 의미있는 시간이라

typingdog.tistory.com

 

먼저, 그래프를 만드는데 있어서 연결 리스트 자료구조를 이용한다. 그래서.. 하 골치 아프게거니 하다가 C++ STL의 리스트를 그냥 활용할까 했는데, 그래도 내가 전에 만든거 테스트도 할 겸, 그걸로 하자! 라고 생각해서 내가 만든 리스트를 100% 신뢰하며 쓰기로 결정했다. 

이미 구현된 자료구조는 100% 신뢰하며 쓰는 것이 국룰이다.

그래프 초기화 및 정점 추가

그래프를 초기화 한다는 것은 코드 작성자 나름이고 기능에 따라 더 무언가 있겠지만, 나는 정점을 추가하는 용도로 사용되었다. 

간선 추가 

이런 식으로 간선이 연결되며 연결된 그래프의 모양은 대충 다음과 같다.

그래프 출력

각 정점 노드 별로 리스트 내에 제공하는 show 관련 함수를 이용하여 출력을 해주면 된다!

출력 결과는 다음과 같다.

리스트 구현할 때, 헤드 부분을 바보 같이 잘못 이해하고 잘못 구현했었다.. 그냥 가리키는 포인터로 두면 될 것을 노드로 처리해버려서.. 아무튼 노란 부분은 무시해도 전혀 문제 없다.

그래프 소멸

 

그래프가 겁나 어려울 줄 알았는데 생각보다 구현하기 쉬웠다. 다만 탐색에 대한 부분들이 들어가 있지 않기 때문에 모르고 하는 소리일 수 있으나, 진짜 생각했던 것 만큼 어렵지는 않다. 다만 그래프는 이전 자료구조들을 많이 활용하기 때문에 처음부터 구현하기에는 조금 까다로운 것은 사실이다.

다음 포스팅은 탐색에 대해서 하나하나 기록해볼 것이다.

---

코드들이다. 그래프 실행 결과는 이미 위에 있기 때문에 코드만 업로드한다.

그래프 코드

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
#include "NonPublishing/Graph/linkedlistforgraph.h"
 
struct DefaultGraph
{
    int vertex_number; // 정점의 수
    int edge_number; // 간선의 수
    ListManager* lm; // 간선을 관리하는 매니저들
};
typedef struct DefaultGraph dgraph;
 
void GraphInit(dgraph* graph, int vertex_number);
void AddEdge(dgraph* graph, int from, int to);
void ShowGraphEdgeInfo(dgraph* graph);
void DestroyGraph(dgraph* graph);
 
 
int main(void)
{
    dgraph graph;
    GraphInit(&graph, 5);
 
    //0 1 2 3 4
    AddEdge(&graph, 01);
    AddEdge(&graph, 03);
    AddEdge(&graph, 12);
    AddEdge(&graph, 23);
    AddEdge(&graph, 34);
    AddEdge(&graph, 40);
 
    ShowGraphEdgeInfo(&graph);
    DestroyGraph(&graph);
 
    return 0;
}
 
void GraphInit(dgraph* graph, int vertex_number)
{
    int i = 0;
    graph->vertex_number = vertex_number;
    graph->edge_number = 0;
    graph->lm = (ListManager*)malloc(sizeof(ListManager) * vertex_number);
 
    for (i = 0; i < vertex_number; i++)
        ListInit(&(graph->lm[i]));
 
    return;
}
 
void AddEdge(dgraph* graph, int from, int to)
{
    if ((from >= graph->vertex_number) || (to >= graph->vertex_number))
    {
        printf("초과된 vertex 값이 왔습니다. \n");
        return;
    }
 
    if (from == to)
    {
        printf("잘못된 vertex 값이 왔습니다. \n");
        return;
    }
 
    LInsert(&(graph->lm[from]), to);
    LInsert(&(graph->lm[to]), from);
    graph->edge_number++;
 
    return;
}
void ShowGraphEdgeInfo(dgraph* graph)
{
    int i = 0;
 
    for (i = 0; i < graph->vertex_number; i++)
    {
        printf("정점[%d] : ", i);
        ShowList(&(graph->lm[i]));
    }
    return;
}
 
void DestroyGraph(dgraph* graph)
{
    int i = 0;
 
    if (graph->lm == NULL)
        return;
 
    for (i = 0; i < graph->vertex_number; i++)
        DeleteList(&(graph->lm[i])); // 내부 연결된 노드를 모두 제거.
    free(graph->lm); // 접점들 자체를 제거.
 
    return;
}
cs

 

리스트 자료구조 헤더 코드

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
#pragma once
 
#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);
 
 
 
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
반응형
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
반응형

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

기본 형태는 다음과 같다.

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

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

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
반응형

삽입 정렬 어렵진 않은데 뭔가 설명을 적기에는 복잡하다 휴 영상이면 좋을텐데 말이지

일단 기록해둔다.

삽입 정렬이란?

삽입해서 정렬하는 것이다. 예를 들어서 

이와 같은 배열이 있을 때, 아래의 그림 순으로 설명하겠다.

그림으로 설명을 좀 대체했다. 나중에 알아먹을 수 있을란가 모르겠군.

그리고 각 그림 속 번호는 그림을 처음 봤을 때 읽어야 할 순서의 동글뱅이 번호를 뜻한다.

코드 및 결과

----

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
#include <stdio.h>
 
void InsertSort(int arr[], int n)
{
    int i = 0, j = 0;
    int target_value = 0;
 
    for (i = 1; i < n; i++// 각 i가 조정할 타겟이 된다.
    {
        target_value = arr[i]; // 삽입할 target value를 따로 저장한다.
        for (j = i-1; j >= 0; j--)
        { // 역순으로 가는 것이니 j가 j+1 보다 먼저이며 앞에서 뒤로 땡기는 행위를 할 것이다.
            if (arr[j] > target_value) // 앞에 것과 비교했을 때 값이 크면 오름차순 위배 
                arr[j+1= arr[j]; // 뒤로 땡긴다.
            else // 오름차순에 맞으므로 
                break// 일단 반복문 하나를 탈출하고,
        }
        arr[j+1= target_value; // 방금 비교한 앞에 요소(arr[j]) 바로 뒤에 빈 공간에 대입
    }
    
    return;
}
 
int main(void)
{
    int i = 0;
    int arr[4= { 3, 5, 9, 1 };
    InsertSort(arr, sizeof(arr) / sizeof(int));
    
    for (i = 0; i < 4; i++)
        printf("%d, ", arr[i]);
    fputc('\n', stdout);
    return 0;
}
cs

----

 

삽입 정렬 성능

이미 정렬되어있는 부분들이 많을수록 성능은 좋다.

최악의 경우 비교, 대입 횟수는 버블 정렬과 마찬가지로 O(n^2) 이다
왜냐하면 안쪽, 바깥쪽 반복의 횟수가 일치하기 때문이다.

최악의 경우 비교 횟수 : (n-1) + (n-2) + ... + 2 + 1 -> O(n^2)
최악의 경우 대입 횟수 : (n-1) + (n-2) + ... + 2 + 1 -> O(n^2)

728x90
반응형
728x90
반응형

 

오늘도 짤막하게 코딩해보고 바로 포스팅한다. 이렇게 하니 매우 여유있게 보고 정리할 수 있는 것 같아서 좋다.
선택 정렬 또한 간단하다 ㅋ

선택 정렬이란?

예시임

위와 같은 배열이 있다. 이를 오름차순으로 정렬할 예정이다.

버블 정렬이 이전 요소들 앞뒤를 비교해가면서 결국, 맨 마지막 대상을 확정 짓는 것이라면 선택 정렬은 반대이다.

맨 왼쪽 요소를 확정 지어가면서 범위를 좁혀나가는 것인데, 버블 정렬처럼 비교한 후 더 변경할 조건이 맞는 경우 값을 교환하는 것이 아니라 변경할 '인덱스'를 저장해 둔 후, 자리를 확정 지을 때 이 인덱스를 활용하여 교환을 한다.

일련의 프로세스 정리

!! 수정 !! :: 3번 동그라미에서 1(인덱스 2)1(인덱스 3)로 변경되어야 합니다!!!!!!!!!!!!!!!!!!!!!

코드 및 실행 결과

----

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
 
void SelectSort(int arr[], int n)
{
    int min_index = -1;
    int temp = -1;
 
    int i = 0, j = 0;
    for (i = 0; i < n-1; i++// 마지막에서 -1 인덱스까지만 확정 지으면 나머지 하나는 자동 확정~
    {
        min_index = i; // 오름차순이라는 가정 하에, 가장 작은 값이 존재하는 인덱스
        for (j = i+1; j < n; j++// 확정 지을 자리를 비교할 필요가 없으므로 i + 1
        {
            if (arr[min_index] > arr[j])
                min_index = j;
        }
        temp = arr[i];
        arr[i] = arr[min_index];
        arr[min_index] = temp;
    }
    return;
}
 
int main(void)
{
    int i = 0;
    int arr[4= { 5931 };
    SelectSort(arr, sizeof(arr) / sizeof(int));
    
    for (i = 0; i < 4; i++)
        printf("%d, ", arr[i]);
    fputc('\n', stdout);
    return 0;
}
cs

9번 라인에서 i의 조건을 n-1 미만으로 두는 이유를 다음 그림을 통해서 설명하겠다.

 

----

 

 

선택 정렬 성능

최악의 경우 비교 횟수 : (n-1) + (n-2) + ... + 2 + 1 -> O(n^2)
최악의 경우 대입 횟수 : n-1 회 무조건 대입이 일어난다. -> O(n)

버블 정렬에 비하면 양호하지만 버블 정렬에서는 최선의 경우에는 한 번도 대입 연산이 없을 수 있으므로, 비교하기 난감할 뿐이다.

728x90
반응형

+ Recent posts