다리를 지나는 트럭

큐를 이용해 푸는 프로그래머스 level 2 문제

어려워서 문제 해설을 찾아봤다. 큐 2개를 이용해 풀었다.

대기하는 차(truck_weight)가 1대 이상 존재하고, 차가 도로위에 올라갈 수 있다면 도로에 진입시킨다.

(도로가 견딜 수 있는 무게를 고려한다.)

도로에 올라가있는 차량 리스트를 queue로 확인 한다. ( queue count )

도로에 올라가있는 차량은 1초에 1씩 이동한다.

도로를 지나고 있는 차량의 남은 거리를 queue로 관리한다. ( queue bridge_on )

차를 pop해서 차의 남은 거리를 1씩 감소시키고, 다시 push 한다.

대기차가 없어도 도로에 차가 있다면 차가 1씩 이동하고 있으므로 시간은 1초씩 계속 흐른다.

 

코드

#include <string>
#include <vector>
#include <queue>
using namespace std;

int solution(int bridge_length, int weight, vector<int> truck_weights) {

    // 시간
    int answer = 0;

    // 도로 위 차 
    queue<int> count;

    // 차마다 가야할 남은 거리 (도로를 얼마나 더 가야 하는지)  
    queue<int> bridge_on;

    // 도로 위의 차 무게 합계
    int current_weight = 0;

    int i = 0;

    while(true){

        //차들이 가야할 거리 
        int togo_size = bridge_on.size();

        // 차를 도로에 진입시킨다.  
        for(i = 0; i < togo_size; i++){

            // 제일 앞에 가고있는 차의 남은 거리  
            int len_front = bridge_on.front();

            bridge_on.pop();

            if(len_front <= 1){ // 남은 거리가 1 이하라면, 이 차의 무게를 제외시킨다 
                current_weight -= count.front();
                count.pop();
                continue;
            } else{
                //  거리 감소시키고 다시 push  
                bridge_on.push(--len_front); 
            }
        } 

        // 도로의 제한무게 만큼 차를 집어넣는다. 대기 차량이 1개 이상 있어야 함.  
        if(truck_weights[0] + current_weight <= weight && truck_weights.size() > 0 ) {

            count.push(truck_weights[0]); // 도로에 차 추가

            current_weight += truck_weights[0]; // 현재 도로 무게에 차 무게 추가  

            truck_weights.erase(truck_weights.begin());  // 대기 차량 삭제  

            bridge_on.push(bridge_length); // 이 차가 지나야할 도로 거리 추가  


        }

        answer++; // 시간이 지나감 

        if(count.size() == 0 && truck_weights.size() == 0){
            break;
        } 

    }

    return answer;
}

참고 포스팅

728x90

'알고리즘 > 프로그래머스' 카테고리의 다른 글

시저 암호  (0) 2020.07.17
문자열 다루기 기본  (0) 2020.07.17
소수찾기 (에라토스테네스의 체)  (0) 2020.07.16
쇠막대기  (0) 2020.07.15
문자열 내림차순으로 배치하기  (0) 2020.07.15

소수찾기 (에라토스테네스의 체)

에라토스테네스의 체 방식을 써야 효율성케이스까지 패스할 수 있는 문제다. (프로그래머스 level 1)

 

문제

문제링크

1부터 입력받은 숫자 n 사이에 있는 소수의 개수를 반환하는 함수, solution을 만들어 보세요.

소수는 1과 자기 자신으로만 나누어지는 수를 의미합니다.
(1은 소수가 아닙니다.)

제한 조건: n은 2이상 1000000이하의 자연수입니다.

 

리뷰

n이 커지면 시간초과가 난다.
에라토스테네스의 체 를 이용해야한다.

 

구하려는 범위 크기 + 1만큼의 배열을 선언한다.

1000000 이라면, int arr[1000001]선언.

arr[0]과 arr[1]은 사용하지 않는다. 0과 1은 소수가 아니기 때문이다.

arr의 값이 0이면 소수, 1이면 소수가 아니다.

 

일단 arr배열의 값은 전부 0으로 초기화 하여 처음에는 소수라고 본다.

만일, arr[i]가 0이면(숫자 i가 소수라면), i이후의 i의 배수들은 i를 약수로 가진 수들이다.

 

따라서 i 이후의 i의 배수에 대해 1값을 준다. (소수가 아니다.)

arr[i]가 1이면 i는 이미 소수가 아니므로 i의 배수 역시 소수가 아니다.

이렇게 배수들을 제외한다.

어느 수의 배수라는 것은 소수가 아니라는 뜻이기 때문이다.

( 이 문장이 한 번에 이해가 안 됬었다, 그치만 직접 수를 써보면서 몇 번 다시 읽어보면 이해할 수 있었다. )

 

2가 소수라면(arr[2] == 0) , 2의 배수들을 지운다. (arr[2의배수들] == 1)

3이 소수라면 (arr[3] == 0), 3의 배수들을 지운다. (arr[3의 배수들] == 1)

 

이런식으로 진행한다.

 

지워야 할 수가 겹치는 문제

2의 배수들: 4, 6, 8, 10, 12, 14 를 지운다.

3의 배수들: 6, 9, 12, 15 .. 를 지운다.

6과 12처럼 지워야할 수의 겹침을 피하려면, 제곱한 수 부터 배수를 지우면 된다.

 

예를 들어, 5의 배수를 지워야한다면 5_2, 즉 10부터 지우는게 아니라 5의 제곱 25부터 지운다.

 

25 + (1 * 5), 25 + (2 * 5), 25 + (3 * 5) , ... 그러면 이전에 지워진 배수들을 건너 뛰게 된다.

 

코드

#include <stdio.h>
#include <vector>
using namespace std;

// 소수 찾기  프로그래머스  

/*
1부터 입력받은 숫자 n 사이에 있는 소수의 개수를 반환하는 함수, solution을 만들어 보세요.
소수는 1과 자기 자신으로만 나누어지는 수를 의미합니다.
(1은 소수가 아닙니다.)
n은 2이상 1000000이하의 자연수입니다.
*/


int arr[1000001]; 
// arr[0]과 arr[1]은 사용하지 않는다.  0과 1은 소수가 아니기 때문. 
// 소수면 0, 소수가 아니면 1값을 갖는다. 

int solution(int n) {

    int answer = 0;
    int i = 0, j = 0;

    for (i = 2; i * i <= n; i++)
    {
        if (arr[i] == 0){ // i가 소수라면, i의 배수는 소수가 아니다.  

            // 유의: i의 제곱부터 지우기 시작한다.  
            for (j = i * i; j <= n; j += i)
                arr[j] = 1; // 소수가 아니므로 1값 할당. 
        }

    }

    for(i = 2; i <= n; i++){ // 소수로 남겨진 것들의 개수를 센다  
        if(arr[i] == 0) answer++;
    }

    return answer;
}

 

도움이 되셨다면 '공감'을 눌러주세요 :)

728x90

'알고리즘 > 프로그래머스' 카테고리의 다른 글

문자열 다루기 기본  (0) 2020.07.17
다리를 지나는 트럭  (0) 2020.07.16
쇠막대기  (0) 2020.07.15
문자열 내림차순으로 배치하기  (0) 2020.07.15
H-index  (0) 2020.06.30

쇠막대기

문제링크

스택/큐 를 주제로 많은 사람들이 풀어보는 쇠막대기 문제다. (프로그래머스 level 2)

리뷰

괄호가 레이저 또는 막대를 표현한다.

그래서 여는 괄호와 닫는 괄호의 짝이 맞고 반드시 여는 괄호부터 시작된다.

여는괄호와 닫는괄호가 연속한 것을 레이저로 인식하도록 했다. 그래서 open_flag를 두었다.

레이저가 아니고서야 닫는괄호가 나오면 막대의 끝인 것이다.

레이저가 끝나면 스택에 있는 여는괄호 수 만큼 더해준다.

막대가 끝나면 막대하나가 끝난 거니까 +1 해준다.

#include <string>
#include <vector>
#include <stack>
using namespace std;


int solution(string arrangement) {

    int answer = 0;
    int i, len = 0;
    stack<char> st;
    bool open_flag = true;

    len = arrangement.size();

    for(i=0; i<len; i++){
        // printf("%c\n", arrangement[i]);
        char pa = arrangement[i];

        if(pa == '(') { // 여는 괄호라면  
            st.push(pa);
            open_flag = true;

        }else{ // ')' 닫는 괄호라면  
            st.pop();
            if(open_flag){ // 레이저  
                answer += st.size();
            }else{
                answer++;
            }
            open_flag = false;


        }
    }

    return answer;
}
728x90

'알고리즘 > 프로그래머스' 카테고리의 다른 글

문자열 다루기 기본  (0) 2020.07.17
다리를 지나는 트럭  (0) 2020.07.16
소수찾기 (에라토스테네스의 체)  (0) 2020.07.16
문자열 내림차순으로 배치하기  (0) 2020.07.15
H-index  (0) 2020.06.30

리뷰

문자열 내림차순으로 배치하기 프로그래머스 level 1 문제

C++에서 제공하는 퀵소트로 구현된 오름차순 정렬 sort() 함수를 썼다.

#include <algorithm>
void sort(T start, T end, Compare comp)

세번째에 기준 인자, 함수를 줄 수 있다.

내림차순은 greater<자료형>() 오름차순은 less<자료형>()

  • sort(s.begin(), s.end()); // 기본은 오름차순 정렬
  • sort(s.begin(), s.end(), greater<자료형>());
  • sort(s.begin(), s.end(), less<자료형>());

헤더파일을 inlude 하고 greater, less, plus minus 를 사용할 수 있다.

코드

#include <stdio.h>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;


string solution(string s) {

    sort(s.begin(), s.end(), greater<char>());
    return s;

}

int main(void)
{
    string input = "Zbcdefg";    
    string answer = "";

    answer = solution(input);

    // string 클래스를 char* 형태로 변환할 때는 c_str() 
    printf("%s", answer.c_str());
    // 출력내용 : gfedcbZ 

    return 0;
}
728x90

'알고리즘 > 프로그래머스' 카테고리의 다른 글

문자열 다루기 기본  (0) 2020.07.17
다리를 지나는 트럭  (0) 2020.07.16
소수찾기 (에라토스테네스의 체)  (0) 2020.07.16
쇠막대기  (0) 2020.07.15
H-index  (0) 2020.06.30

H-index

문제링크
프로그래머스 level2 문제다.

1시간 끙끙대다가 다른분들의 풀이법을 읽어봤다.
[0] 인경우 0이고, [7]인경우 1이었다.
[30, 19, 9, 1] 인 경우에도 답은 3이었다. 테스트케이스를 여러개 보니까 조금 감이 왔다.

참고링크

리뷰

문제에 나와있는 테스트케이스에서
[3, 0, 9, 2, 6] 이라면, 답은 3이라고 생각했다.
하지만, 다른분들의 풀이법을 읽어보니 인용수 배열중에 답이 꼭 있는것이 아니었다.
그리고 정렬을 하면 수월했다.
정렬 해두고, 현재 인덱스보다 작아진다면 탈출하면 답이 나온다.

#include <stdio.h>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

// H-index (프로그래머스) 

int solution(vector<int> citations){

    int answer = 0;

    // 정렬 
    sort(citations.begin(), citations.end(), greater<int>()); 

    while(answer < citations.size()){

        if(citations[answer] <= answer){ // 인덱스 보다 작아지는 순간 종료
            break;
        }else{
            answer++;
        }
    }

    return answer;
}
728x90

'알고리즘 > 프로그래머스' 카테고리의 다른 글

문자열 다루기 기본  (0) 2020.07.17
다리를 지나는 트럭  (0) 2020.07.16
소수찾기 (에라토스테네스의 체)  (0) 2020.07.16
쇠막대기  (0) 2020.07.15
문자열 내림차순으로 배치하기  (0) 2020.07.15

문제

왕자 N명이 있고, 이 중에서 공주를 구하러 갈 왕자를 정하기로 했다.
왕자들의 나이 순으로 1번 부터 N번까지 번호를 매기고 동그랗게 둘러 앉는다.
1번 왕자부터 시계방향으로 돌아가며 1부터 시작하여 번호를 외치게 한다.
한 왕자가 K(특정숫자)를 외치면 그 왕자는 공주를 구하러 가는데서 제외되고 원 밖으로 나오게 된다.
그리고 다음 왕자부터 다시 1부터 시작하여 번호를 외친다.
이렇게 해서 마지막까지 남은 왕자가 공주를 구하러 갈 수 있다.


예를 들어 총 8명의 왕자가 있고, 3을 외친 왕자가 제외된다고 하자.
처음에는 3번 왕자가 3을 외쳐 제외된다.
이어 6, 1, 5, 2, 8, 4번 왕자가 차례대로 제외되고 마지막까지 남게 된 7번 왕자가 공주를 구하러간다.


N과 K가 주어질 때 공주를 구하러 갈 왕자의 번호를 출력하는 프로그램을 작성하시오.

  • 입력설명
    첫 줄에 자연수 N(5<=N<=1,000)과 K(2<=K<=9)가 주어진다.
    8 3

  • 출력설명
    첫 줄에 마지막 남은 왕자의 번호를 출력합니다.
    7


구조

인덱스를 1부터 이용하기 위해 벡터를 n+1 크기로 생성한다.
0의 개수를 k 번째 까지 센다. 개수는 cnt 변수에 누적한다.
k번째 왕자의 값을 1로 변경한다.
break point 가 n-1 이 되면 중단한다.


코드


#include <stdio.h>
#include <vector>
#include <algorithm>
using namespace std;

int main(int argc, char** argv){

    int prince = 0, limit = 0, pos = 0, i = 0, bp = 0, cnt=0;
    //freopen("input.txt", "rt", stdin);
    scanf("%d %d", &prince, &limit);

    vector<int> arr(prince+1);

    while(bp != prince-1){
        pos++;

        if(pos > prince){
            pos = 1;
        }

        if(arr[pos] == 0){
            cnt++;

            if(cnt == limit){ // 특정 번째의 왕자를 아웃시킴.  
                arr[pos] = 1;
                cnt = 0;
                bp++; // 아웃시킨 왕자의 개수를 센다.  
            }
        }
    } 

    for(i=1; i<=prince; i++){
        if(arr[i] == 0){
            printf("%d", i);
            break;
        }
    }

    return 0;
}
728x90

우선순위 큐


보통의 큐 인데 '우선순위'속성을 갖는 자료구조이다.

삽입(Enqueue) 할 때 데이터에 우선순위를 부여한다.

삭제(Dequeue) 할 때 가장 높은 우선순위를 갖는 요소부터 제거한다.

우선순위 큐의 삽입 연산

최소값이나 최대값을 우선순위 기준으로 둔다. 

새 요소가 들어오면 값에 따라 적당한 위치를 탐색해야 한다.

일반적으로 힙 순서 속성을 활용하여 구현한다. 

우선순위 큐의 제거 연산

전단을 제거하면 끝난다.
전단에는 우선순위가 가장 높은 값(여기서는 최소값) 이 있기 때문이다.

다음은 우선순위 큐의 구현에 필요한 '힙'에 관한 내용이다.


힙은 힙 순서 속성을 만족하는 완전이진트리다.

힙은 루트노드를 제거하는 최소값 제거 연산과 새 노드 삽입 연산이 있다.

힙 순서 속성이란?

트리 내의 모든 노드가 부모 노드 보다 큰 값을 가진다는 규칙이다.

완전이진트리란?

최고 깊이를 제외한 모든 깊이의 노드들이 완전히 채워져 있는 이진트리다.

힙에서 가장 작은 데이터를 갖는 노드는 루트 노드이다.

힙에 새 노드 삽입하기

힙 순서 속성을 만족시킬 때 까지 부모와 값을 비교한다.

삽입의 과정은 아래와 같다.

  1. 힙의 최고 깊이, 최 우측에 새 노드를 삽입한다.
  2. 새 노드의 부모와 새 노드를 비교한다. 부모 노드가 값이 더 크다면 종료한다. 그렇지 않으면 다음을 진행한다.
  3. 새 노드가 더 작다면, 부모와 위치를 교환한다. 2번으로 간다.

힙의 최소값 삭제

힙의 최소값은 항상 루트노드다.
루트 노드를 삭제하고 나서, 힙 순서 속성을 유지하는 작업이 주를 이룬다.

최소값 삭제 과정은 아래와 같다.

  1. 힙의 루트에 최고 깊이 최 우측 노드를 옮겨온다.
  2. 옮겨온 노드의 양쪽 자식을 비교하여, 둘 중에 더 작은 자식과 교환한다.
  3. 옮겨온 노드가 양쪽 자식보다 작거나, 잎노드가 되면 연산을 종료한다. 그렇지 않으면 2번, 3번을 반복한다.

힙 예제 구현 해보기


링크드리스트 VS 배열

링크드 리스트로 구현하면, "힙의 가장 마지막 노드"를 찾는데 효율적인 답을 찾기 어렵다.
힙은 완전이진트리 이므로 배열인덱스를 통해 자식과 부모의 위치를 찾아내는 데 좋다.

  • k번 인덱스 노드의 양쪽 자식 노드 인덱스 찾기
    • 왼쪽 : 2k + 1
    • 오른쪽 : 2k +2
  • k번 노드의 부모 인덱스 찾기
    • (k-1)/2 의 몫

힙의 자료구조

typedef struct Node { // 노드 
    int data;
}HeapNode;

typedef struct Heap { // 힙 
    HeapNode* nodes;
    int capacity; // 힙의 용량
    int usedSize; // 힙에 들어있는 노드 개수  
}Heap;

새 노드 삽입하기

Insert()는 아래 두 함수를 활용한다.

GetParent()는 노드의 인덱스를 입력받아서 부모노드 인덱스를 반환한다.
SwapNodes()는 노드와 부모노드를 교환한다.

void Insert(Heap* H, int newData) { 

    // 1. 최고 깊이 최 우측 노드와 그 것의 부모 노드 인덱스
    int currentPosition = H->usedSize;
    int parentPosition = GetParent(currentPosition);

    // 2. 만약, 힙이 꽉차있다면, 용량 증가 
    if (H->capacity == H->usedSize) {

        H->capacity *= 2;
        H->nodes = (HeapNode*)realloc(H->nodes, sizeof(HeapNode) * H->capacity);
    }

    // 3. 최고 깊이 최 우측 노드에 새 노드 삽입 
    H->nodes[currentPosition].data = newData; 

    // 4. 힙 순서 속성 유지할 때까지 비교 반복
    // 탈출 조건 : 자신이 루트 노드가 된다(인덱스 0). 또는 자신의 값이 부모보다 크거나 같다. 
    while (currentPosition != 0 && H->nodes[currentPosition].data < H->nodes[parentPosition].data) {

        SwapNodes(H, currentPosition, parentPosition); // 자리 교환 

        currentPosition = parentPosition; // 1번 처럼 인덱스 갱신 
        parentPosition = GetParent(currentPosition);
    }

    // 5. 용량 증가
    H->usedSize++; 

    return;
}

힙의 최소값 삭제하기

최소값은 항상 루트노드가 가지고 있다.

  1. 함수는 일단 루트노드의 최소값을 기억해 두는 것으로 시작한다.
  2. 루트노드 자리에 최고 깊이 최 우측 노드를 복사해서 옮겨둔다.
  3. 새로 온 노드는 힙 순서 속성을 만족할 때 까지 자신의 자식 노드와 비교하여 교환을 반복한다.
void DeleteMin(Heap* H, HeapNode* root) { 

    int parentPosition = 0; // 루트노드 자신이 부모로 시작. 루트니까 인덱스 0.
    int leftPosition = 0;
    int rightPosition = 0;

    // root에 최소값을 저장해둔다. 
    memcpy(root, &H->nodes[0], sizeof(HeapNode));
    memset(&H->nodes[0], 0, sizeof(HeapNode));

    H->usedSize--; // 최우측 노드 인덱스 
    SwapNodes(H, 0, H->usedSize); // 루트 자리에 최고 깊이 최우측 노드를 옮겨온다. 

    leftPosition = GetLeftChild(0); // 루트 노드의 왼쪽 자식, 오른쪽 자식 인덱스 
    rightPosition = leftPosition + 1;


    while (1) { // 힙 순서 속성을 만족할 때 까지 루트는 자기 자식과 비교한다.

        int selectedChild = 0; // 1. 부모노드(루트노드)와 교환할 노드의 인덱스를 찾아본다 

        if (leftPosition >= H->usedSize) { 
            // 실제 노드 개수보다 왼쪽자식 인덱스가 같거나 크다 
            // 유의점 : 노드 인덱스는 0부터 시작. 사용량은 1부터 시작
            // 즉, 루트노드는 잎노드가 된 상황. 
            break; 
        }

        if (rightPosition >= H->usedSize) { 
            // 사용 인덱스보다 오른쪽 자식인덱스가 크다
            // 즉, 루트노드에게 오른쪽 자식은 존재하지 않고 왼쪽자식만 있는것임.
            selectedChild = leftPosition; // 타겟은 왼쪽자식 
        }
        else { // 양쪽 자식 존재 

            if (H->nodes[leftPosition].data < H->nodes[rightPosition].data) { 
                // 왼쪽 자식이 작다. 타겟은 왼쪽자식 
                selectedChild = leftPosition; 
            }
            else { // 오른쪽 자식이 작다. 타겟은 오른쪽 자식  
                selectedChild = rightPosition;
            }
        }

        // 2. 값을 보고 교환할 필요가 있는지 확인한다 
        if (H->nodes[selectedChild].data < H->nodes[parentPosition].data) {
            SwapNodes(H, selectedChild, parentPosition);
            parentPosition = selectedChild; // 타겟이 새 부모가 된다. 
        }
        else {
            break;
        }

        // 3. 왼쪽 자식, 오른쪽 자식 인덱스를 갱신한다. 
        leftPosition = GetLeftChild(parentPosition);
        rightPosition = leftPosition + 1;
    }

    // 4. 용량의 반 이하만 쓰고 있다면, 힙 용량을 반으로 줄인다. 
    if (H->usedSize < (H->capacity/2)) {

        H->capacity /= 2; // 줄일 용량 
        H->nodes = (HeapNode*)realloc(H->nodes, sizeof(HeapNode) * H->capacity);

    }

}

힙 예제 전체 코드

헤더파일 heap.h
Insert() 와 Delete()를 포함한 함수가 구현된 heap.cpp
테스트 해보는 heap_test.cpp

 

heap.h

더보기
#ifndef HEAP_H
#define HEAP_H

// 힙 

#include <stdio.h>
#include <memory.h> // memcpy
#include <stdlib.h> // realloc

typedef int ElementType;

typedef struct Node { // 노드 
    ElementType data;
}HeapNode;

typedef struct Heap { // 힙 
    HeapNode* nodes;
    int capacity;
    int usedSize;
}Heap;

Heap* Create(int initialSize);
void Destroy(Heap* H); // 
void Insert(Heap* H, ElementType newData);  // 삽입
void DeleteMin(Heap* H, HeapNode* root); // 최소값 삭제 
int GetParent(int index); // 부모 인덱스 리턴
int GetLeftChild(int index); // 왼쪽 자식 인덱스 리턴
void SwapNodes(Heap* H, int index1, int index2);
void PrintNodes(Heap* H);

#endif

 

heap.cpp

더보기
#include "heap.h"

// 힙 메모리 할당 
Heap* Create(int initialSize) {

    Heap* newHeap = (Heap*)malloc(sizeof(Heap));

    newHeap->nodes = (HeapNode*)malloc(sizeof(HeapNode) * initialSize);
    newHeap->capacity = initialSize;
    newHeap->usedSize = 0;

    printf("size:%d\n", sizeof(HeapNode));

    return newHeap;
}

// 힙 메모리 해제 
void Destroy(Heap* H) {

    free(H->nodes);
    free(H);

    return;
}

// 삽입 
void Insert(Heap* H, int newData) { 

    // 1. 최고 깊이 최 우측 노드와 그 것의 부모 노드 인덱스
    int currentPosition = H->usedSize;
    int parentPosition = GetParent(currentPosition);

    // 2. 만약, 힙이 꽉차있다면, 용량 증가 
    if (H->capacity == H->usedSize) {

        H->capacity *= 2;
        H->nodes = (HeapNode*)realloc(H->nodes, sizeof(HeapNode) * H->capacity);
    }

    // 3. 최고 깊이 최 우측 노드에 새 노드 삽입 
    H->nodes[currentPosition].data = newData; 

    // 4. 힙 순서 속성 유지할 때까지 비교 반복
    // 탈출 조건 : 자신이 루트 노드가 된다(인덱스 0). 또는 자신의 값이 부모보다 크거나 같다. 
    while (currentPosition != 0 && H->nodes[currentPosition].data < H->nodes[parentPosition].data) {

        SwapNodes(H, currentPosition, parentPosition); // 자리 교환 

        currentPosition = parentPosition; // 1번 처럼 인덱스 갱신 
        parentPosition = GetParent(currentPosition);
    }

    // 5. 용량 증가
    H->usedSize++; 

    return;
}


// 최소값 삭제 
void DeleteMin(Heap* H, HeapNode* root) { 

    int parentPosition = 0; // 루트노드 자신이 부모로 시작. 루트니까 인덱스 0.
    int leftPosition = 0;
    int rightPosition = 0;

    // root에 최소값을 저장해둔다. 
    memcpy(root, &H->nodes[0], sizeof(HeapNode));
    memset(&H->nodes[0], 0, sizeof(HeapNode));

    H->usedSize--; // 최우측 노드 인덱스 
    SwapNodes(H, 0, H->usedSize); // 루트 자리에 최고 깊이 최우측 노드를 옮겨온다. 

    leftPosition = GetLeftChild(0); // 루트 노드의 왼쪽 자식, 오른쪽 자식 인덱스 
    rightPosition = leftPosition + 1;


    while (1) { // 힙 순서 속성을 만족할 때 까지 루트는 자기 자식과 비교한다.

        int selectedChild = 0; // 1. 부모노드(루트노드)와 교환할 노드의 인덱스를 찾아본다 

        if (leftPosition >= H->usedSize) { 
            // 실제 노드 개수보다 왼쪽자식 인덱스가 같거나 크다 
            // 유의점 : 노드 인덱스는 0부터 시작. 사용량은 1부터 시작
            // 즉, 루트노드는 잎노드가 된 상황. 
            break; 
        }

        if (rightPosition >= H->usedSize) { 
            // 사용 인덱스보다 오른쪽 자식인덱스가 크다
            // 즉, 루트노드에게 오른쪽 자식은 존재하지 않고 왼쪽자식만 있는것임.
            selectedChild = leftPosition; // 타겟은 왼쪽자식 
        }
        else { // 양쪽 자식 존재 

            if (H->nodes[leftPosition].data < H->nodes[rightPosition].data) { 
                // 왼쪽 자식이 작다. 타겟은 왼쪽자식 
                selectedChild = leftPosition; 
            }
            else { // 오른쪽 자식이 작다. 타겟은 오른쪽 자식  
                selectedChild = rightPosition;
            }
        }

        // 2. 값을 보고 교환할 필요가 있는지 확인한다 
        if (H->nodes[selectedChild].data < H->nodes[parentPosition].data) {
            SwapNodes(H, selectedChild, parentPosition);
            parentPosition = selectedChild; // 타겟이 새 부모가 된다. 
        }
        else {
            break;
        }

        // 3. 왼쪽 자식, 오른쪽 자식 인덱스를 갱신한다. 
        leftPosition = GetLeftChild(parentPosition);
        rightPosition = leftPosition + 1;
    }

    // 4. 용량의 반 이하만 쓰고 있다면, 힙 용량을 반으로 줄인다. 
    if (H->usedSize < (H->capacity/2)) {

        H->capacity /= 2; // 줄일 용량 
        H->nodes = (HeapNode*)realloc(H->nodes, sizeof(HeapNode) * H->capacity);

    }

}

// 현재 노드와 부모 노드 교환 
void SwapNodes(Heap* H, int current, int parent) {

    int copySize = sizeof(HeapNode);
    HeapNode* tempNode = (HeapNode*)malloc(sizeof(HeapNode));

    memcpy(tempNode, &H->nodes[current], copySize);
    memcpy(&H->nodes[current], &H->nodes[parent], copySize);
    memcpy(&H->nodes[parent], tempNode, copySize);

    free(tempNode);

    return;
}

int GetParent(int index) { // 부모의 인덱스를 반환 
    return (int)((index - 1) / 2);
}

int GetLeftChild(int index) { // 왼쪽 자식 인덱스 반환 
    return (index * 2) + 1;
}

void PrintNodes(Heap* H) { // 노드 전체 출력 
    int i = 0;

    for (i = 0; i < H->usedSize; i++) {
        printf("%d ", H->nodes[i].data);
    }
    printf("\n");
}

heap_test.cpp

더보기
#include "heap.h"

// 힙 

int main(void) {

	Heap* H = Create(3);
	HeapNode minNode;

	Insert(H, 12); // 6개 삽입 
	Insert(H, 87);
	Insert(H, 111);
	Insert(H, 34);
	Insert(H, 16);
	Insert(H, 75);

	PrintNodes(H);

	DeleteMin(H, &minNode); // 삭제 후 출력 하는 것을 반복 
	PrintNodes(H);

	DeleteMin(H, &minNode);
	PrintNodes(H);

	DeleteMin(H, &minNode);
	PrintNodes(H);

	DeleteMin(H, &minNode);
	PrintNodes(H);

	DeleteMin(H, &minNode);
	PrintNodes(H);

	DeleteMin(H, &minNode);
	PrintNodes(H);

	Destroy(H);

	return 0;
}

 

실행 화면 

 

 


힙을 이용한 우선순위 큐 예제

힙은 최소값이나 최대값을 바로 얻어낼 수 있으므로 우선순위 큐 구현에 적합하다.

힙 예제에 썼던 코드와 우선순위 큐 예제가 다른 점은 아래와 같다.

  • HeapNode 구조체에 Priority 필드 추가
  • HeapNode의 data 필드 자료형을 void*로 변경

 

헤더파일 priorityQueue.h

함수들을 구현한 priorityQueue.cpp

테스트하는 main 함수 priorityQueue_test.cpp 3개로 구성됬다. 

 

실행결과 화면 

priorityQueue.h

더보기
#ifndef PRIORITYQUEUE_H
#define PRIORITYQUEUE_H

#include <stdio.h>
#include <memory.h>
#include <stdlib.h>

typedef int PriorityType;

typedef struct PQNode {
	PriorityType priority;
	void* data;
}PQNode;

typedef struct PQ {
	
	PQNode* nodes;
	int capacity;
	int usedSize;

}PriorityQueue;

PriorityQueue* PQ_Create(int initialSize);
void PQ_Destory(PriorityQueue* PQ);
void PQ_Enqueue(PriorityQueue* PQ, PQNode newNode);
void PQ_Dequeue(PriorityQueue* PQ, PQNode* root);
int PQ_GetParent(int index);
int PQ_GetLeftChild(int index);
void PQ_SwapNodes(PriorityQueue* PQ, int index1, int index2);
int PQ_IsEmpty(PriorityQueue* PQ);

#endif

 

priorityQueue.cpp

더보기
#include "priorityQueue.h"

// 우선순위 큐 생성 
PriorityQueue* PQ_Create(int initialSize) {
	
	PriorityQueue* PQ = (PriorityQueue*)malloc(sizeof(PriorityQueue));
	PQ->nodes = (PQNode*)malloc(sizeof(PQNode) * initialSize);
	PQ->capacity = initialSize;
	PQ->usedSize = 0;

	return PQ;
}

void PQ_Destory(PriorityQueue* PQ) {

	free(PQ->nodes);
	free(PQ);

	return;
}

// 삽입 
void PQ_Enqueue(PriorityQueue* PQ, PQNode newNode) {

	int currentPosition = PQ->usedSize;
	int parentPosition = PQ_GetParent(currentPosition);

	// 1. 용량 확인 
	if (PQ->usedSize == PQ->capacity) {

		if (PQ->capacity == 0) {
			PQ->capacity = 1;
		}

		PQ->capacity *= 2; // 용량 2배 증가
		PQ->nodes = (PQNode*)realloc(PQ->nodes, sizeof(PQNode) * PQ->capacity);
	}

	// 2. 삽입
	PQ->nodes[currentPosition] = newNode;

	// 3. '힙 순서 속성' 만족시킬 때 까지 후속처리
	while (currentPosition != 0 &&
		PQ->nodes[currentPosition].priority < PQ->nodes[parentPosition].priority) {

		PQ_SwapNodes(PQ, currentPosition, parentPosition); // 자신과 부모 위치 교환 

		currentPosition = parentPosition; // 인덱스 갱신 
		parentPosition = PQ_GetParent(currentPosition);
	}

	PQ->usedSize++;

	return;
}


// 최소값 삭제 
void PQ_Dequeue(PriorityQueue* PQ, PQNode* root) {

	int parentPosition = 0; // 루트노드 자리에서 시작한다. 
	int leftPosition = 0;
	int rightPosition = 0;

	// 1. 루트노드의 값을 root에 저장해둔다. 
	memcpy(root, &PQ->nodes[0], sizeof(PQNode));
	memset(&PQ->nodes[0], 0, sizeof(PQNode)); // 0으로 초기화 

	// 2. 최고 깊이 최우측 노드를 root 자리에 옮긴다. 
	PQ->usedSize--;
	PQ_SwapNodes(PQ, 0, PQ->usedSize);

	leftPosition = PQ_GetLeftChild(0); // 루트의 왼쪽 자식
	rightPosition = leftPosition + 1;  // 루튼의 오른쪽 자식

	// 3. 자신과 자신의 양쪽 자식을 비교 
	while (1) {
		
		int targetPosition = 0;

		// 비교할 자식을 고름 
		if (leftPosition >= PQ->usedSize) { // 자식 없음
			break;
		}
		else if (rightPosition >= PQ->usedSize) { // 왼쪽 자식만 존재 
			targetPosition = leftPosition;
		}
		else {
			// 양쪽 자식 존재 

			if (PQ->nodes[leftPosition].priority < PQ->nodes[rightPosition].priority) {
				targetPosition = leftPosition;
			}
			else {
				targetPosition = rightPosition;
			}

		}

		// 자신과 자식의 값 비교해서 교환 필요 여부 확인 
		if (PQ->nodes[targetPosition].priority < PQ->nodes[parentPosition].priority) {
			PQ_SwapNodes(PQ, targetPosition, parentPosition);
		}
		else {
			break; // 탈출 
		}

		// 인덱스 갱신 
		leftPosition = PQ_GetLeftChild(parentPosition);
		rightPosition = leftPosition + 1;
	}

	// 4. 힙 용량 관리

	if (PQ->usedSize < PQ->capacity / 2) {
		// 용량 감소 

		PQ->capacity /= 2; 
		PQ->nodes = (PQNode*)realloc(PQ->nodes, sizeof(PQNode) * PQ->capacity);
	}

	return;
}

// 노드 교환 
void PQ_SwapNodes(PriorityQueue* PQ, int index1, int index2) {

	int NodeSize = sizeof(PQNode);
	PQNode* tempNode = (PQNode*)malloc(NodeSize);

	memcpy(tempNode, &PQ->nodes[index1], NodeSize);
	memcpy(&PQ->nodes[index1], &PQ->nodes[index2], NodeSize);
	memcpy(&PQ->nodes[index2], tempNode, NodeSize);

	free(tempNode);
	return;
}

int PQ_GetParent(int index) { // 부모 노드 인덱스 반환 
	return (int)((index - 1) / 2);
}
int PQ_GetLeftChild(int index) { //왼쪽 자식 인덱스 반환 
	return (index * 2) + 1;
}

int PQ_IsEmpty(PriorityQueue* PQ) { // 비어서 사용량이 0인지 반환 
	return (0 == PQ->usedSize);
}

 

priorityQueue_test.cpp

더보기
#include "priorityQueue.h"

void PrintNode(PQNode* node) {
	printf("작업명: %s (우선순위:%d)\n", (char*)node->data, node->priority);
}

int main(void) {

	// PQ를 크기를 입력하여 생성한다. 
	PriorityQueue* PQ = PQ_Create(3);

	PQNode Popped;

	PQNode Nodes[7] = {
		{34, (void*)"코딩"},
		{12, (void*)"고객미팅"},
		{87, (void*)"화분물주기"},
		{45, (void*)"문서작성"},
		{35, (void*)"디버깅"},
		{66, (void*)"이닦기"},
	};
	
	// 큐에 넣는다. 
	PQ_Enqueue(PQ, Nodes[0]);
	PQ_Enqueue(PQ, Nodes[1]);
	PQ_Enqueue(PQ, Nodes[2]);
	PQ_Enqueue(PQ, Nodes[3]);
	PQ_Enqueue(PQ, Nodes[4]);
	PQ_Enqueue(PQ, Nodes[5]);

	printf("큐에 남아있는 작업 수 %d\n", PQ->usedSize);

	while (!PQ_IsEmpty(PQ)) {
		PQ_Dequeue(PQ, &Popped);
		PrintNode(&Popped);
	}

	return 0;
}
728x90

'알고리즘 > 알고리즘 C' 카테고리의 다른 글

최소 스패닝 트리  (0) 2020.08.12
그래프의 정의와 표현방법  (2) 2020.08.02
삽입 정렬 (중요)  (0) 2020.06.02
버블 정렬  (0) 2020.05.30
균형 이진 탐색 트리 (AVL트리)  (1) 2020.03.30

+ Recent posts