Skip to main content

Command Palette

Search for a command to run...

C/C++ 큐(Queue)

Published
3 min read

개요

큐(Queue)는 자료구조의 한 형태로 스택과 다르게 FIFO(First In First Out) 형태의 자료구조이다. FIFO는 말 그대로 선입선출이라는 의미로 먼저 들어간 데이터가 먼저 나온다나는 것이다. 쉽게 생각하여 표를 받기 위해 줄을 서는 것으로 보면 된다. 표를 받기 위해 먼저 줄을 서면 먼저 표를 받는 방식이다. 너비 우선 탐색, 캐시 구현 등의 다양한 곳에 사용된다. 큐의 구조와 큐를 구성하는 메소드 들에 대해 알아보자.

큐 구조체

struct Queue
{
    int size;
    int front;
    int rear;
    int* Q;
};

큐에는 줄의 시작(front)와 끝(rear)이 존재하며, 데이터가 통하는 구멍이 두 개라 볼 수 있다. size는 큐가 데이터를 담을 수 있는 개수를 나타내며, Q는 할당할 배열 포인터이다.

create함수-큐 생성 함수

void create(Queue *q, int size)
{
    q->size = size;
    q->front = q->rear = -1;
    q->Q = new int[q->size];
}

선언한 큐 구조체에 사이즈를 채우고, Q에 배열을 할당한다. front와 rear는 각각 배열의 인덱스를 나타내는데 아직 큐가 비어 있으므로 -1 인덱스를 할당한다.

enqueue함수-큐 데이터 입력함수

void enqueue(Queue *q, int x)
{
    if (q->rear == q->size -1)
        printf("Queue is Full");
    else
    {
        q->rear++;
        q->Q[q->rear] = x;
    }
}

먼저 rear이 큐의 끝에 도달했는지 체크한다. 데이터가 입력될 때마다, 큐의 rear이 증가하고 그 자리에 데이터가 들어간다. rear이 마지막 인덱스에 있으면 더 이상 데이터를 넣지 못한다. 아래의 그림을 보면 이해가 쉬울 것이다.

dequeue함수-데이터를 출력하는 함수

int dequeue(Queue *q)
{
    int x = -1;

    if (q->front == q->rear)
        printf("Queue is Empty\n");
    else
    {
        q->front++;
        x = q->Q[q->front];
    }
    return x;
}

front는 쉽게 말해 표를 주는 맨 앞 점원이라 보면 된다. front 뒤에 데이터가 존재하며, front를 통해 데이터가 나간다고 보면 된다. front와 rear 사이에 데이터가 저장되는데 front와 rear의 위치가 같다면 그 사이에 공간이 없다는 뜻이기에 큐가 비었음을 알 수 있다. 그게 아니라면, front의 위치가 한 칸 밀리고, 거기에 있는 데이터를 빼낸다.

큐의 데이터가 빠져나가는데 왜 배열의 데이터가 안 밀리냐고 궁금할 수 있다. 데이터가 나가는 위치를 고정해 놓고 데이터(표를 받는 사람)이 앞으로 움직이는 것은 소요가 크다. 대신에 데이터가 나가는 위치(front, 표를 주는 사람)이 뒤로 움직이면 똑같은 효과를 front만 옮겨서 얻을 수 있어 효율이 좋다.

Display함수-데이터 출력 함수

void Display(Queue q)
{
    for (int i = q.front+1; i <= q.rear; i++)
        printf("%d ", q.Q[i]);
    printf("\n");
}

배열의 값들을 front부터 rear까지 인덱스로 출력한다.

일자 큐의 단점

위에 구현한 일자 큐, 배열의 처음과 끝이 존재하는 큐는 효율이 좋지 않다. 데이터를 넣고 빼는 것을 반복해서 rear이 배열의 끝에 도달하면 front가 밀려 앞의 데이터가 큐에서 빠져나가서 공간이 비더라도 큐는 꽉 찼다고 판단하여 입력이 불가능하다. 이렇듯 일자 큐는 일회성이다. 이를 해결하기 위한 방법이 순환 큐이다. 배열의 끝이 배열의 처음으로 이어져서 rear는 배열의 마지막에서 한 칸 움직이면 배열의 처음으로 움직일 수 있다.

순환 큐 구현하기

순환 큐의 front rear 움직임은 그저 +1을 하지 않는다. 다음과 같은 식으로 움직인다.

rear = (rear + 1) % 배열의 size

이 방식으로 front와 rear는 배열의 끝에서 한 칸 움직이면 나머지 연산에 의해 배열의 처음 인덱스로 이동하게 된다. 똑같이 배열이 빈 것은 front와 rear이 같을 때이며, 큐가 꽉 찬 것은 rear에서 이동한 곳이 front일 때이다. 아래는 empty일 때와 full일 때의 상황이다.

순환 큐 enqueue함수

void enqueue(Queue *q, int x)
{
    if ((q->rear + 1) % q->size == q->front)
        printf("Queue is full");
    else
    {
        q->rear = ((q->rear + 1) % q->size);
        q->Q[q->rear] = x;
    }
}

rear 연산을 통해 다음 이동 자리가 front와 같다면 큐는 꽉 찬 것이며, 그게 아니라면 rear을 한 칸 뒤로 옮긴 뒤 데이터를 넣는다.

순환 큐 dequeue함수

 int dequeue(Queue *q)
{
    int x = -1;

    if (q->front == q->rear)
        printf("Queue is Empty\n");
    else
    {
        q->front = (q->front + 1) % q->size;
        x = q->Q[q->front];
    }
    return x;
}

rear과 front가 같다면 큐는 빈 것이며, 그게 아니라면 front를 뒤로 밀고, 거기의 데이터를 뽑는다.

마무리

큐에 대해 알아보았다. 스택과 다르게 FIFO 방식이며, 일반 큐와 일반 큐의 일회성을 보완한 순환 큐가 있다. 다음 글에서 연결 리스트로 큐를 구현해보고, C++ 클래스로 구현해보자

More from this blog

[ZSH] tree 사용하기

들어가며 큰 규모의 프로젝트를 출시한 뒤, 후일을 위해서 더 늦기 전에 파일 정리 및 문서화를 진행해야했다. 문서화 작업을 하는 중에 기왕 정리하는 거 파일 구조를 이쁘게 트리 구조로 나열하여 코멘트를 달면 나중에 보더라도 이해하기 더 쉬울 것 같았다. 어떻게 해야 간지나는 트리 구조를 만들 수 있을까 방법을 찾다보니 역시나 파일 구조를 트리로 이쁘게 출력해주는 커맨드 툴이 존재했다. tree 커맨드에 대해서 알아보고 알짜배기 내용만 정리했다....

Feb 21, 20242 min read

[Next.js] parallel routes & intercepting routes

트위터 로그인 모달창을 만들어보며 넥스트의 parallel routes 와 intercepting routes 을 학습한 내용을 정리해보았습니다. 트위터 로그인 창을 확인해봅시다. 루트 디렉토리 화면을 배경으로 i/flow/login 페이지가 동시에 표시되고 있습니다. 저는 app router 를 학습하기 전까지는 createPortal 을 사용하여 포탈 영역에 로그인 컴포넌트를 띄우는 방식을 사용했었습니다. const NoLogin =()=...

Feb 1, 20244 min read

C/C++ 이진 트리(binary tree) 개요 및 구현(1)

개요 트리는 노드들이 나무 가지처럼 연결된 비선형 계층적 자료구조이다. 하위 트리가 존재하고, 그 노드에 또 하위 트리가 존재하는 자료구조 이다. 트리의 맨 위에 있는 루트 노드가 존재한다. 우리가 알아볼 트리는 이진 트리이다. 이진 트리는 자식 노드(부모로부터 아래로 이어진 노드)가 2개 이하인 구조를 말한다. 트리의 사용 사례로는 다음과 같다 계층 적 데이터 저장(파일,폴더) 효율적인 검색 속도 힙 데이터 베이스의 인덱싱 트리에 ...

Jan 31, 20244 min read

[React] Server component (RSC)

React.js 18 에 도입된 리액트 서버 컴포넌트는 서버에서 동작하는 리액트 컴포넌트를 의미합니다. Next가 권장하는 라우팅 방식인 app router의 기반이 되는 컴포넌트이기 때문에 app router 를 이해하기 위해서는 server component 에 대한 이해가 필요합니다. server component 리액트는 클라이언트단만을 컴포넌트화하는 대신, server component라는 개념을 통해 서버 영역을 컴포넌트화합니다. ...

Jan 29, 20243 min read

Flutter, JavaScript

42 posts