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

큐란?

컨테이너를 확장 및 축소하여 기능을 제한하거나 특정 기능과 특징을 사용하도록 만든 것을 컨테이너 어댑터라고 하는데, 큐 또한 바로 그것이다. 

내부적으로 dequeue, list 등의 컨테이너로 동작하며 기본적으로는 dequeue로 동작한다. vector는 안된다! 왜냐하면 vector는 앞에서 값을 빼는 pop 기능이 없기 때문이다.( vector::push_back( )과 vector::pop_back( )만 존재함 )

사용되고 있는 내부 컨테이너를 변경하려면 queue<data type, list<int>> listack; 이러한 식으로 진행하면 된다. ( 스택에서 사용될 data type, 변경할 컨테이너 구조: list or dequeue등)

코드 및 실행 결과

----

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)
{
    queue<string> qu;
    string words[3= {
        "Apple",
        "Banana",
        "Corn"
    };
 
    for (int i = 0; i < 3; i++)
        qu.push(words[i]);
 
    while (!qu.empty())
    {
        cout << qu.front() << endl;
        qu.pop();
    }
    return 0;
}
cs

----

728x90
반응형

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

Iterator 사용 방법 예시  (0) 2021.01.05
Priority Queue  (0) 2021.01.04
Stack  (0) 2021.01.03
Tuple  (0) 2021.01.03
Pair  (0) 2021.01.03
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
반응형
728x90
반응형

스택이란?

컨테이너를 확장 및 축소하여 기능을 제한하거나 특정 기능과 특징을 사용하도록 만든 것을 컨테이너 어댑터라고 하는데, 스택이 바로 그것이다.

내부적으로 vector, dequeue, list 등의 컨테이너로 동작하며 기본적으로는 dequeue로 동작한다.

내부 컨테이너를 변경하려면 stack<data type, list<int>> listack; 이러한 식으로 진행하면 된다. ( 스택에서 사용될 data type, 변경할 컨테이너 구조: list or vector 등)

코드 및 실행 결과

----

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 <stack>
#include <iostream>
#include <string>
 
using namespace std;
 
int main(void)
{
    stack<string> st;
    string words[3= {
        "Apple",
        "Banana",
        "Corn"
    };
 
    for (int i = 0; i < 3; i++)
        st.push(words[i]);
    
    while (!st.empty())
    {
        cout << st.top() << endl;
        st.pop();
    }
    return 0;
}
cs

----

728x90
반응형

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

Priority Queue  (0) 2021.01.04
Queue  (0) 2021.01.04
Tuple  (0) 2021.01.03
Pair  (0) 2021.01.03
Map  (0) 2020.09.15
728x90
반응형

튜플이란?

Tuple 은 Pair 처럼 쌍으로 데이터를 묶거나, 쌍으로 반환 등을 할 때 사용되는 STL 이다.

#include <tuple>에 정의되어 있고, make_tuple 함수를 이용하여 생성이 가능하다.

코드 및 실행 결과

----

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
#include <iostream>
#include <tuple>
 
using namespace std;
 
int main(void)
{
    // 일반적인 추가
    tuple<intchardouble> t1;
    t1 = make_tuple(1'a'1.1);
    
    cout << "get<0>(t1) : " << get<0>(t1) << "/ get<1>(t1) : " << get<1>(t1) << "/ get<2>(t1) : " << get<2>(t1) << endl;
    
    // 튜플 내부 요소 분해하기
    int n = 0
    char ch = 'z'
    double lf = 0.0;
 
    std::tie(n, ignore, lf) = t1; // ignore 로 무시한다.
    cout << " n : " << n << " / ch : " << ch << " / lf : " << lf << endl;
    std::tie(n, ch, lf) = t1;
    cout << " n : " << n << " / ch : " << ch << " / lf : " << lf << endl;
 
    // 에러가 난다.
    //for (int i = 0; i < 3; i++)
    //    cout << get<i>(t1) << endl; // Error 
 
    return 0;
}
cs

----

 

728x90
반응형

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

Queue  (0) 2021.01.04
Stack  (0) 2021.01.03
Pair  (0) 2021.01.03
Map  (0) 2020.09.15
Set  (0) 2020.09.15
728x90
반응형

Pair

Pair 는 두 자료형을 묶을 수 있다( 무조건 2개를 묶는다 )
first 키워드를 통해 pair 쌍 중 첫 번째 값에, second를 통해 Pair 쌍 중 두 번째 값에 접근 가능하다.

구조체와 템플릿을 이용한 개념이라고 보면 된다.

코드 및 실행 결과

----

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 <vector>
#include <utility>
 
using namespace std;
 
int main(void)
{
    // 일반적인 추가
    pair<intchar> p1;
    p1.first = 1;
    p1.second = 'a';
    
    cout << "p1.first : " << p1.first << " p1.second : " << p1.second << endl;
 
    // make_pair을 통한 추가
    pair<intchar> p2 = make_pair(2'b');
 
    cout << "p2.first : " << p2.first << " p2.second : " << p2.second << endl;
 
    // vector에 값을 넣을 때
    vector<pair<intchar>> pv;
    pv.push_back(make_pair(3'c'));
 
    cout << "pv[0].first : " << pv[0].first << " pv[0].second : " << pv[0].second << endl;
 
    return 0;
}
cs

----

 

728x90
반응형

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

Stack  (0) 2021.01.03
Tuple  (0) 2021.01.03
Map  (0) 2020.09.15
Set  (0) 2020.09.15
List  (0) 2020.09.14
728x90
반응형

버블 정렬을 학습 후에 바로 정리한다.

버블 정렬이란?

위와 같은 4칸 크기의 배열 구조가 존재하고 그 안에 값이 정렬되지 않은 채로 존재한다. 여기서 우리는 오름차순으로 값을 정렬하고자 한다.

각 화살표 시작에 적힌 원 숫자 순서대로 값을 결정하는 방법이다. 어떻게? 인덱스를 하나씩 증가시켜가면서 인덱스와 뒤 인덱스를 비교하여 값의 위치를 조정하는 것. 그것이 버블 정렬이다.

총 4개의 값이 들어왔다고 가정.

3번째 자리, 2번째 자리, 1번째 자리, 0번째 자리 순으로 값이 정해져야 한다.

(1) 3번째 자리에 들어올 값을 정하기 위해서는 [0]과 [1](0+1)을 비교, [1]과 [2](1+1)를 비교, [2]와 [3](2+1)을 비교 ( 3번 반복 ) -> 3번째 자리에 들어올 값 결정


(2) 2번째 자리에 들어올 값을 정하기 위해서는 [0]과 [1](0+1)을 비교, [1]과 [2](1+1)를 비교 ( 2번 반복 ) -> 2번째 자리에 들어올 값 결정
(3) 1번째 자리에 들어올 값을 정하기 위해서는 [0]과 [1](0+1)을 비교 ( 1번 반복 ) -> 1번째 자리에 들어올 값 결정
(4) 0번째 자리에 들어올 값은 자연스럽게 결정

결국 총 3번 반복하는데 그 반복 하나하나마다 3번, 2번, 1번 반복을 해야한다.
이를 코드로 나타낸다면

for ( ... )
{
    for( ... )
    {
        ...
    }
}

이러한 형태가 되는 것이다.

코드 구현 및 실행 결과

----

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>
 
void BubbleSort(int* arr, int n)
{
    int i = 0, j = 0;
    int temp = 0;
 
    for (i = 0; i < n - 1; i++)
    {
        for (j = 0; j < n - 1 - i; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                temp = arr[j + 1];
                arr[j + 1= arr[j];
                arr[j] = temp;
            }
        }
    }
    return;
}
 
int main(void)
{
    int arr[4= { 591002 };
    int i = 0;
 
    BubbleSort(arr, sizeof(arr) / sizeof(int));
 
    for (i = 0; i < 4; i++)
        printf("%d, ", arr[i]);
    fputc('\n', stdout);
 
    return 0;
}
cs

i의 역할은 j가 어느 값까지만 접근 가능한지를 결정해주며, j는 i의 제한만큼만 순회하면서 각 i에 해당하는 최대 j값 + 1 번째 인덱스를 가장 큰 값으로 세팅해놓는다.

----

 

버블 정렬 성능

최악의 경우 비교 횟수 : (n-1) + (n-2) + ... + 2 + 1 -> O(n^2)
최악의 경우 대입 횟수 : 비교 후 바로 대입이 진행되므로 O(n^2), temp 연산으로 인한 *3 -> 3 * O(n^2) -> O(n^2)

728x90
반응형
728x90
반응형

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

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

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

단순 배열 리스트란?

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

배열

배열 리스트의 장점

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

배열 리스트의 단점

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

코드 구현 및 실행

---

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <stdio.h>
#define MAX_LIST_SIZE 20
 
struct List
{
    int insert_index;
    int search_index;
    int arr[MAX_LIST_SIZE];
};
 
void ListInit(struct List* list)
{
    int i = 0;
    list->insert_index = 0// 값을 추가할 때 사용하는 인덱스 -> 모든 값은 0 ~ (insert_index-1) 까지만 유효함!
    list->search_index = 0// 탐색을 위한 순회 시 사용하는 인덱스
 
    // 모두 0으로 초기화
    for (i = 0; i < MAX_LIST_SIZE; i++)
        list->arr[i] = 0;
    return;
}
void LInsert(struct List* list, int value)
{
    if (list->insert_index >= MAX_LIST_SIZE) 
    {
        printf("저장이 불가능합니다.\n");
        return;
    }
    list->arr[list->insert_index++= value;
    return;
}
int LCount(struct List* list) // 현재 추가된 수
{
    return list->insert_index;
}
 
int LFirst(struct List* list, int* data) // 첫 번째 요소에 인덱스를 위치시키는 역할이라고 보면 된다.
{
    if (list->insert_index >= 1// 한 번이라도 값이 추가된 적이 있는 경우에만 사용.
    {
        list->search_index = 0;
        *data = list->arr[list->search_index];
        return 1;
    }
    // search_index를 증가시키지 않는다. 필요하다면 증가를 미리 시키고 접근해야함.
    return 0;
}
int LNext(struct List* list, int* data) // LFirst 함수에 이어 바로 다음 요소에 인덱스를 위치시키는 역할이라고 보면 된다.
{
    if ((list->search_index + 1< list->insert_index) // 접근해야하는 인덱스(search_index+1)가 유효한 인덱스 범위 내에 존재한다면
    {
        *data = list->arr[++(list->search_index)];
        return 1;
    }
    return 0;
}
void LRemove(struct List* list) // 현재 위치한 인덱스의 값을 제거하고, 빈 요소 자리를 채우는 작업까지 진행한다.
{
    int target = 0;
 
    for (target = list->search_index; target < (list->insert_index - 1); target++// 유효 인덱스의 바로 전 인덱스까지만 가서 땡겨오면 끝.
        list->arr[target] = list->arr[target + 1];
    list->search_index--;
    list->insert_index--;
 
    printf("\n");
 
    return;
}
 
int main(void)
{
    struct List list;
    int data;
    ListInit(&list);
 
    LInsert(&list, 11);
    LInsert(&list, 11);
    LInsert(&list, 22);
    LInsert(&list, 22);
    LInsert(&list, 33);
 
 
    if (LFirst(&list, &data))
    {
        printf("%d ", data);
        while (LNext(&list, &data))
            printf("%d ", data);
    }
 
    if (LFirst(&list, &data))
    {
        if (data == 22)
            LRemove(&list);
        while (LNext(&list, &data))
        {
            if (data == 22)
                LRemove(&list);
        }
    }
 
    if (LFirst(&list, &data))
    {
        printf("%d ", data);
        while (LNext(&list, &data))
            printf("%d ", data);
    }
 
    printf("\n\n");
    return 0;
}
cs

---

 

728x90
반응형
728x90
반응형

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

자료구조 공부의 순서에 상관없이 그냥 올리려고 한다.

오늘은 우선 순위 큐를 위한 Heap 자료 구조에 대해서 정리한다. 큐 형태로 포장하지 않았을 뿐이지, 큐를 염두하여 만들었기 때문에 큐라고 이름만 바꾸면 우선 순위 큐가 될 수 있다.

Heap 이란 무엇인가?

힙이란 다음 그림과 같은 형태이다.

Heap

Q : 엥? 이건 (완전) 이진 트리가 아닌가? 
A : 그렇다. 맞다
- 빼박 이진 트리 아닌가? 의미의 '완전'이 아니다, 리프 노드를 제외한 모든 노드들은 두 자식 노드를 꼭 채워진 형태의 트리, 리프 노드는 꼭 왼쪽부터 채워지는 트리를 의미하는 '완전 이진 트리'

이 친구들은 이진 트리의 형태를 하고 있으나, 일반 이진 트리와는 달리 좀 많은 규칙들로 탄탄히 채워진 트리이다. 이 규칙들을 통해 힙의 특징을 설명할 것이다.

Heap 의 특징

1. 완전 이진 트리의 특성을 갖는다.
리프 노드를 제외한 모든 노드들은 두 자식 노드를 모두 채운 상태여야하며, 리프 노드는 채울 때, 왼쪽부터 채워져야 한다.

2. 자식 노드와 부모 노드 간의 규칙이 존재한다.
힙은 밑에서 부터 쌓아 올리는 자료 구조이기 때문에 부모 노드와 자식 노드 간 특정한 규칙이 존재한다. 예를 들어, 자식 노드는 부모보다 커야만 한다, 자식 노드는 부모보다 작아야만 한다 등의 규칙이다. 

3. 일반적인 트리와 달리, 왼쪽 자식과 오른쪽 자식은 부모 노드 기준으로 값이 작은 노드와 큰 노드로 취급하지 않는다. 
그저 리프 노드의 자식을 채울 때, 왼쪽에서 오른쪽 순으로 먼저 채우는 것 뿐이다.

 

힙 구조에 값을 삽입


트리의 가장 끝에 삽입 후 부모와 비교하여 위치를 조정한다.

 

힙 구조에서 값을 삭제

삭제할 때에는 탐색 후 삭제가 아니라, Pop의 개념이다. 루트 노드의 값을 빼준다. 그리고 나서 꼭 트리를 재구성 해줘야하는데 이 때, 루트를 어떤 값으로 채울 것인가에 대한 고민을 해야한다. 그러기 위해서는 가장 마지막 노드를 루트에 넣고 트리를 재구성하여 내려간다.

삭제가 진행되기 전 힙 구조

이와 같은 구조에서 3을 제거하고, 10을 루트에 넣어본다.

10을 루트에 넣고 보니 위배되는 부분들이 존재한다. 이를 풀기 위해 계속 구조를 재조정한다.

힙 자료구조의 재구성이 완료된 모습


이 값을 삭제하는 부분으로 인해 우선 순위 큐의 구현이 가능하다!!!

우선 순위 큐란 무엇인가? 우선 순위가 높은 것을 먼저 빼는 것이다. 큐의 선입선출 이딴 거 다 필요없고, 언제 들어가든 간에 우선 순위가 높은 것이 먼저 나오는 것이다.
단, 우선 순위가 같은 경우에는 선입선출이 보장되어야 한다. 이는 리프노드의 왼쪽 삽입 우선 특징(1번 특징)으로 인해서 같은 우선 순위의 경우 선입 선출을 보장하도록 프로그램을 작성할 수 있다. 

즉, 힙의 루트 노드는 삽입, 삭제 과정에서의 힙 자료구조의 재조정을 통해 각 힙에서 지정한 규칙에 의거하여 우선 순위가 높은 값으로 채워넣어진다.
예를 들어

Max Heap의 경우에는 루트 노드로 올라갈수록 값이 커짐 -> 큰 값일 수록 우선 순위가 높다.
Min Heap의 경우에는 루트 노드로 올라갈수록 값이 작아짐 -> 작은 값일 수록 우선 순위가 높다.

사용자 정의 Heap의 경우에는 구조체 등을 이용하여 우선 순위 변수를 따로 두고 진행한다면 이 또한 우선 순위를 매길 수 있다.

이렇게 힙 구조에서 루트 노드에서 값을 빼냄으로써 우선 순위대로 값을 취할 수 있기 때문에 heap을 통한 우선 순위 큐를 구현할 수 있는 것이다.

힙의 구현

힙을 구현할 때에는 배열로 구현할 것이다. 왜냐?

그 이유를 알기 이전에 있어서 힙을 어떤 자료 구조로 구현할 수 있는지부터 확인해보자.

1. 완전 이진 트리이므로, 연결 리스트의 구조로 당연히 구현이 가능하다.
2. 그리고 인덱스 조절을 통한 배열로도 가능하다.

그렇다면 왜 배열로 구현할 것인가?
우리는 삽입 연산을 진행할 때, 트리의 가장 마지막 노드 자리에 값을 삽입한다. 만약 단순 연결 리스트로 구현했다면 이 마지막까지 순회하기에 비용이 많이 든다. 그리고 부모, 자식 간의 노드 비교가 많기 때문에 비교적 비용이 적게 드는 랜덤 엑세스가 가능한 배열로 구현하기로 했다. (라고 윤성우 교수님의 자료구조 서적에 나와있다 ㅋ)

그래서 인덱스 제어 방법이 필요하다.

1. 배열의 인덱스는 1부터 시작한다. ( 인덱싱을 용이하게 하기 위함이다. 구현하다보면 안다. 왜 인덱싱을 1부터 하는지 ㅋㅋㅋ)

2. 현재의 인덱스를 넣었을 때, 부모 혹은 자식의 노드를 구하는 방법을 알아야 한다.

위 그림과 같이 계산하면 임의의 인덱스를 기준으로 부모 노드의 인덱스를 구할 수도, 자식 노드를 구할 수 도 있다. 즉, 완전 이진 트리 구조라고 할지라도 인덱스를 통한 랜덤 엑세스가 가능한 것이다.

아래는 그 코드와 주석이다.

---

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
#include <stdio.h>
 
#define TRUE 1
#define FALSE 0
#define HEAP_LEN 100
 
struct heap_element // 정수 값을 저장하는 힙의 노드 구조체로 우선 순위를 지정할 수 있다.
{
    int priority; // 값이 작을수록 우선 순위가 크다.(1등 ~ 10등 ... 개념의 수를 저장함)
    int data;
};
typedef struct heap_element heap_element;
 
struct heap
{
    int count;
    heap_element heap_arr[HEAP_LEN]; // 힙은 배열 구조를 기반으로 구현된다.
};
typedef struct heap heap;
 
void HeapInit(heap* h);
int HIsEmpty(heap* h);
void HInsert(heap* h, int data, int priority);
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 main(void)
{
    heap h;
    HeapInit(&h);
 
    HInsert(&h, 31);
    HInsert(&h, 52);
    HInsert(&h, 105);
    HInsert(&h, 84);
    HInsert(&h, 126);
    HInsert(&h, 73);
 
    printf("%d \n", HDelete(&h));
    ShowHeap(&h);
 
    printf("--- Del --- \n");
    while (!HIsEmpty(&h))
        printf("%d, ", HDelete(&h));
    fputc('\n', stdout);
    ShowHeap(&h);
    return 0;
}
 
 
void HeapInit(heap* h)
{
    int i = 0;
    h->count = 0;
    for (i = 0; i < HEAP_LEN; i++)
    {
        h->heap_arr[i].data = 0;
        h->heap_arr[i].priority = -1;
    }
    return;
}
int HIsEmpty(heap* h)
{
    return (h->count == 0) ? TRUE : FALSE;
}
 
void HInsert(heap* h, int data, int priority)
{
    int insert_index = h->count + 1
    int parent_index = 0;
    heap_element new_element = { priority, data };
     
    while (insert_index != 1// 처음 삽입되는 위치가 루트 노드가 아니면 혹은 갱신된 추가 인덱스가 루트 노드가 아니면
    {
        parent_index = GetParentIndex(insert_index);
        if (new_element.priority >= h->heap_arr[parent_index].priority) // 우선 순위가 부모가 높다면 구조 재조정이 필요 없다.
            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 (last_element.priority <= h->heap_arr[child_index].priority) // 자식 노드보다 우선 순위가 높다면 현재 구한 인덱스로의 변경만 하면 된다.
            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(%d), \n", h->heap_arr[1].data, h->heap_arr[1].priority);
 
    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(%d), ", h->heap_arr[i].data, h->heap_arr[i].priority);
        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->heap_arr[left_child_index].priority > h->heap_arr[right_child_index].priority) // 오른쪽 자식 노드가 더 우선 순위가 높다면
            return right_child_index; // 오른쪽 자식 노드의 인덱스 반환
        else // 왼쪽 자식 노드가 더 우선 순위가 높거나 양쪽 노드의 우선 순위가 같다면
            return left_child_index; // 왼쪽 자식 노드의 인덱스 반환
    }
}
 
 
cs

---

728x90
반응형

+ Recent posts