오늘 한 일 

 

알고리즘 스터디에 다녀왔다. 

입력받는 방법에 대해서 아리까리 한 부분이 많았었는데, 오늘 다른 사람들 것과 내 코드를 비교해보고 토론하면서 몰랐던 것들을 다시 정리했다. 

 

문자열을 입력받는 경우. 

scanf("%s" ) 문자열과 개행문자 까지 입력받는다. 

다음 문자열을 입력받고 싶은 경우, 버퍼에 들어가있는 개행 문자를 제거하기 위해 getchar() 또는 fflush()를 써야 한다. 

fgets()를 이용하면 문자열만 입력받는다. 

 

문제 푸는 것이 재밋기도 하고 어렵기도 한데, 다같이 푸니까 너무 좋다. 계속 스터디를 하고 싶다.  

 

ESL 용역일을 시작했다. 팀장님과 킥오프를 했다. 임베디드C를 하는 프로젝트는 처음이라 설렜다.

시료를 만지작거리니까 재미있었다.  

 

TrueSTUDIO라는 IDE를 다운받았다. 

일본인 개발자가 만든 코드를 분석하기 시작했다. 영어로 된 주석을 잘 달아 놓으셔서 멋지다고 생각했다. 

변수명을 역할과 기능으로 상세히 지어놓은 것들이 인상적이었다. 유지보수를 위해 그렇게 한 것이다. 

나도 변수명을 좀더 고심해서 정하도록 해야겠다. 

728x90

'일상 > Today I Learn(TIL)' 카테고리의 다른 글

2020-06-21 TIL  (0) 2020.06.21
2020-06-18 TIL  (0) 2020.06.18
2020-06-10 TIL  (0) 2020.06.10
2020-06-04 TIL  (0) 2020.06.04
2020-06-02 TIL  (0) 2020.06.02

문제

왕자 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

오늘 한 일 

 

취업 대비 스터디에 다녀왔다. (2회차)

서로 코드를 리뷰하다 보니 내 코드를 비롯해 다른 사람들 코드의 장단점을 분석해봤다.

긴장도 됬지만 질문 답변 하는 과정에서 배워가는 것도 많고 재미있었다. 

문제 출제에 관해서 의견을 내봤다. 

 

코드에 대해서 피드백 받은 부분을 정리해봤다. 

알고리즘 문제를 3개 풀어봤다. 

github에 commit 하는 재미가 쏠쏠하다. 

확실히 뭘 하든간에 눈에 보이게 표시가 나면 재미있고 계속 할 수 있도록 동기부여가 된다. 

728x90

'일상 > Today I Learn(TIL)' 카테고리의 다른 글

2020-06-18 TIL  (0) 2020.06.18
2020-06-17 TIL  (0) 2020.06.17
2020-06-04 TIL  (0) 2020.06.04
2020-06-02 TIL  (0) 2020.06.02
2020-06-01 TIL  (0) 2020.06.01

오늘 한 일 

 

알고리즘 문제 4개를 풀었다. 

LRU문제가 특히 기억에 남는다.  for문 인덱스 하나를 잘못해서 테스트케이스를 전부 통과하지 못했다. 

그래서 엄청 골머리를 썩혔다. 

흠.. 디버깅을 더 꼼꼼하게 해야겠다. 

 

우선순위 큐와 힙을 공부했다. 

내일은 글을 다듬어서 포스팅 업로드를 해야겠다. 

 

728x90

'일상 > Today I Learn(TIL)' 카테고리의 다른 글

2020-06-17 TIL  (0) 2020.06.17
2020-06-10 TIL  (0) 2020.06.10
2020-06-02 TIL  (0) 2020.06.02
2020-06-01 TIL  (0) 2020.06.01
2020-05-27 TIL  (0) 2020.05.27

우선순위 큐


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

삽입(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

오늘 한 일 

 

알고리즘 스터디의 문제 2개를 풀었다. 

ASCII 코드에서 숫자 하나가 틀려서 1시간 반을 애먹었던게 참 한숨이 나온다. 

앞으로는 더 잘 확인해야겠다. 

 

삽입정렬도 다시 짜서 포스팅했다. 

포스팅하며 정리하는게 다시 기억을 되살리는데 도움되서 자꾸 하게 된다.

가끔 그림 그릴때가 귀찮긴 하지만 그것도 재미있다.  

728x90

'일상 > Today I Learn(TIL)' 카테고리의 다른 글

2020-06-10 TIL  (0) 2020.06.10
2020-06-04 TIL  (0) 2020.06.04
2020-06-01 TIL  (0) 2020.06.01
2020-05-27 TIL  (0) 2020.05.27
2020-05-25 TIL  (0) 2020.05.25

삽입정렬

삽입정렬이란?

삽입정렬은 크기가 n인 집합일 때,
2개, 3개, 4개 ... n-1개씩 비교대상이 늘어나는 것이 특징이다.

단, 사이클마다 대상 집합이 이미 정렬되어 있는 경우에는 비교가 일어나지 않는다. 그래서 무조건(정렬이 되어 있든 아니든) 비교하는 버블정렬보다 평균적으로는 성능이 좋다.

아래에서 예시로 정렬할 때 오름차순을 목표로 한다.

특징

1.정렬 대상이 처음에는 2개.
사이클을 반복할 때마다 1개씩 커집니다. 최대 n-1까지 커진다.

2.정렬 대상의 가장 오른쪽 요소가 대상 중에서 가장 큰 값인지 확인한다.
그렇지 않다면 그 것의 적절한 위치를 찾아준다. 자신보다 큰 요소를 만날 때 까지 탐색한다.

3.적절한 곳을 찾았다면, 자신보다 큰 요소들을 모두 한칸씩 앞으로 이동시킨다. 그리고 새로 생긴 곳에 위치시킨다.

예시

5 1 6 4 2 3

데이터 집합 크기는 6이다.

일단 비교 대상은 2개다. 5와 1.

가장 오른쪽 요소인 1이 가장 큰값을 가지는지 확인해본다.

1을 뽑는다. 1보다 큰 수 5 앞에 놓아야 한다.

5를 이동시켜서 오른쪽으로 밀어서 공간을 만든다.

빈 곳에 1을 삽입한다.

결과 : 1 5 6 4 2 3

비교 대상은 3개다. 1, 5, 6

가장 오른쪽 요소 6이 가장 큰 값을 가지는지 확인한다. (6을 그 앞의 숫자 5와 비교한다.)

6이 더 크다.

그 앞의 요소들은 이미 정렬을 거친 숫자들이니까 확인할 필요가 없다. (!!)

다음 단계로 넘어간다.

비교 대상은 4개다. 1, 5, 6, 4

가장 오른쪽 요소 4가 가장 큰 값을 가지는지 확인한다. (4를 그 앞의 수 6과 비교한다.)

4가 작으니까 이동이 필요하다.

4를 뽑는다.

6, 5, 1 순으로 탐색해보니까 1 앞에 오면 된다.

5와 6을 오른쪽으로 민다. 빈 공간에 4를 삽입한다.

결과 : 1 4 5 6 2 3

삽입정렬 코딩해보기 1

#include <stdio.h>
#include <string.h>

void InsertionSort(int dataset[], int length){
    int i=0, j=0, target=0;

    for(i=1; i<length; i++){

        if(dataset[i-1] < dataset[i]){
            continue; // 맨 오른쪽 요소가 가장 크다면 정렬이 필요 없다. 
        }

        target = dataset[i]; // 맨 오른쪽 요소의 적절한 위치 탐색시작

        for(j=0; j<i; j++){

            if(dataset[j] > target){
                // 데이터를 쭈욱 민다. 
                memmove(&dataset[j+1], &dataset[j], sizeof(dataset[0])*(i-j));
                dataset[j] = target;
                break;
            }
        }
    }
}

int main(void){

    int dataset[] = {5, 3, 6, 4, 1, 2};
    int length = sizeof(dataset) / sizeof(int);
    int i = 0;

    InsertionSort(dataset, length);

    for(i=0; i<length; i++){
        printf("%d ", dataset[i]);
    }

    return 0;
}

memmove() 함수

메모리에 내용을 이동시키는 함수이다. C표준함수.

배열처럼 연속된 데이터를 이동시킬 때 유용하다.

반복문으로 이 함수의 기능을 구현할 수 있지만 성능이 떨어진다.

void* memmove(void* dest, const void *src, size_tn)

dest: 복사의 대상이 되는 목적지 주소

src: 복사할 원본이 있는 주소

size_tn : 복사할 메모리 길이 (byte)

memmove() 함수는 데이터를 '이동'시키고 memcpy() 함수는 데이터를 '복사'한다.

삽입정렬 코딩해보기 2

memmove() 메모리 복사 없이, 배열의 데이터 복사하는 방식으로 정렬해본다.

i와 j로 만든 이중 for문인데,

i는 1부터 시작한다. 비교 집합의 맨 오른쪽 수가 i인데 temp 변수에 기억해둔다.

j 반복문 안에서, temp 보다 큰 수가 있다면 하나씩 오른쪽으로 당긴다.

적절한 위치를 발견하는 순간 temp에 저장된 값을 삽입한다.

input.txt 입력 설명
첫 번째 줄에 자연수 N(1<=N<=100)이 주어집니다.
두 번째 줄에 N개의 자연수가 공백을 사이에 두고 입력됩니다.
6
11 7 5 6 10 9

출력 설명
오름차순으로 정렬된 수열을 출력합니다.
5 6 7 9 10 11

#include <stdio.h> 

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

    int a[100], num=0, temp=0, i=0, j=0;

    freopen("input.txt", "rt", stdin);

    scanf("%d", &num); // 데이터 개수  

    for(i=0; i<num; i++){ // 데이터 저장  
        scanf("%d", &a[i]);
    }

    for(i=1; i<num; i++){ // 오름차순으로 정렬 

        temp = a[i]; // a[i]의 적당한 위치를 탐색 시작  

        for(j=i-1; j >= 0; j--){ // 오른쪽에서 왼쪽으로 순회  

            if(a[j] > temp){  
                a[j+1] = a[j];
            }else{
                break;
            }
        }

        a[j+1] = temp; // 적당한 위치에 삽입. 
    }

    for(i=0; i<num; i++){ // 정렬 결과  
        printf("%d ", a[i]);
    }

    return 0;
}
  • 헷갈렸던 부분 정리!

데이터집합이 3 2 4 5 라고 가정한다.

i는 인덱스 1부터 temp 에는 2 가 들어가 있다.
j는 인덱스 i-1부터 시작하니까 0 이 들어가 있다.

for문의 조건을 확인한다.
j는 0보다 크거나 같으니까, 반복문 안의 코드가 실행된다.
3이 2보다 크니까 if문 조건을 만족한다.
그래서 인덱스 1 자리에 인덱스 0의 값이 복사된다.
그 결과 3 3 4 5 가 된다.
반복문 안의 코드 수행이 끝났으니까, j--가 수행된다.
j는 -1이 된다.
다시 for문의 조건을 확인한다. j>=0 을 만족하지 못하니까 반복문을 종료한다.

j는 -1인 채로 남아 있었으니까, arr[j+1] 은 arr[0]을 의미한다.
그래서 인덱스 0 자리에 temp값 2를 넣는다.
그 결과 2 3 4 5 가 된다.

728x90

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

그래프의 정의와 표현방법  (2) 2020.08.02
우선순위 큐  (0) 2020.06.03
버블 정렬  (0) 2020.05.30
균형 이진 탐색 트리 (AVL트리)  (1) 2020.03.30
이진 탐색 트리 Binary Search Tree (linked list)  (0) 2020.03.18

+ Recent posts