본문 바로가기

Datastructure/[7] 큐

[7] 큐 ② 배열 ADT

728x90
반응형

큐(QUEUE) : 먼저 들어온 데이터가 먼저 나가는 자료구조

선입선출(FIFO: First-In First-Out) 순서를 따른다. (예) 매표소의 대기열

 

큐 ADT

 큐 ADT는 임의의  삽입과 삭제는 선입선출(First-In First-Out, FIFO) 순서를 따른다.
 큐 ADT는 대기열을 추상화한 데이터 구조이다.
 
삽입은 큐의 뒤(rear), 삭제는 큐의 앞(front)이라 불리는 위치에서 수행된다.
큐의 입출력

큐 메쏘드

주요 큐 메쏘드

enqueue(e): 큐의 뒤에 원소를 삽입

 element dequeue(): 큐의 앞에서 원소를 삭제하여 반환

보조 큐 메쏘드

element front(): 큐의 앞에 있는 원소를 (삭제하지 않고) 반환

 integer size(): 큐에 저장된 원소의 수를 반환

 boolean isEmpty(): 큐가 비어 있는지 여부를 반환

 integer elements(): 큐 원소 전체를 반환

예외

 emptyQueueException(): 비어 있는 큐에 대해 삭제 또는 front를 수행 시도할 경우 발령

 fullQueueException(): 만원 큐에 대해 삽입을 수행 시도할 경우 발령

 

 큐 응용

직접 응용 간접 응용
 대기열, 관료적 체제
 공유자원에 대한 접근, 예를 들어 프린터
 멀티프로그래밍
  알고리즘 수행을 위한 보조 데이터구조
  다른 데이터구조를 구성하는 요소

큐 ADT 구현 [1] 선형큐

선형 큐

배열을 선형으로 사용하는 큐 알고리즘

 삽입을 계속하기 위해서는 요소들을 이동시켜야 하므로 기억 장소 활용면에서 비효율적이다.

선형 큐 구현

 크기 N의 배열을 원형으로 사용한다

 두 개의 변수를 사용하여 front와 rear 위치를 기억한다.

 

 f: front 원소의 첨자

 r: rear 원소의 첨자

 

f가 front 원소가 저장된 위치의 한 셀 앞을 가리키도록 정의하는 방식도 가능하나

이에 상응하여 큐 관련 메쏘드 수정이 필요하다.

 

선형 큐

선형 배열을 사용할 때와 달리 원형 배열을 이용하는 경우 원소들이 나란한 상태와 돌아간 상태의 두 가지 형태가 가능하다. 하지만 이에 따라 front와 rear 값의 관계에 의해 큐가 비어 있는지 만원인지 구별해야 한다.

 

이때 다음과 같은 두 가지 전략이 존재한다.

빈 큐 만원 큐

 

a. 한 개의 빈 노드를 사용하지 않고 예비 노드로 동작하도록 한다.

b. 원소 수를 변수에 저장하여 유지한다.

구조체 선언

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

typedef char element;

#define MAX 10

typedef struct {
    element *data; // 데이터 저장 배열
    int front, rear; // 포인터와 리어 변수
    int size;// 배열의 사이즈
} QueueType;

void print(QueueType *Q);
void printQue_R_TO_E(QueueType *Q);

 

이 코드는 크기가 최대 10인 원형 큐를 구현하는 데 사용된다.

 

구조체 QueueType는 큐의 데이터를 저장하기 위한 배열 data, 큐의 시작과 끝을 가리키는 포인터 변수 front와 rear, 그리고 배열의 크기를 나타내는 size 변수로 구성된다.

init 함수

// 큐 초기화 함수
void init(QueueType *Q){
    Q->data = (char*)malloc(sizeof(char)*Q->size);
    Q->front = Q->rear = -1; // 큐의 front와 rear를 초기화 : -1
    //인덱스가 0부터 배열에 실질적 저장
}

• 큐를 초기화하는 데 사용된다.

• 큐의 데이터를 저장할 배열을 동적으로 할당하고, front와 rear 변수를 -1로 초기화하여 큐가 비어있음을 나타낸다.

 

isEmty 함수 / isFull 함수

// 큐가 비어있는지 확인하는 함수
int isEmpty(QueueType *Q){return (Q->front == Q->rear);}

// 큐가 꽉 차 있는지 확인하는 함수
int isFull(QueueType *Q){return (Q->rear == Q->size - 1);}

 

• isEmpty : 큐가 비어있는지를 확인한다.

   ▷ 큐가 비어있으면 front와 rear가 같은 위치를 가리키므로 Q->front == Q->rear를 반환한다.

 

 isFull: 큐가 꽉 차 있는지를 확인한다.

   ▷ 큐가 꽉 차 있으면 rear가 배열의 끝을 가리키므로 Q->rear == Q->size - 1을 반환한다.

 

큐의 상태를 확인하는 데 사용되며 큐에 원소를 삽입하거나 삭제하기 전에 큐의 상태를 확인하여 오버플로우(overflow)나 언더플로우(underflow)를 방지할 수 있다.

enqueue 함수

// 큐에 원소를 삽입하는 함수
void enqueue(QueueType *Q, element e){
    // 포화 상태에서 삽입을 시도한 경우 처리
    if (isFull(Q)) {
        printf("overflow "); 
        // print(Q); 
        printQue_R_TO_E(Q);
        exit(1); 
    } 
    else {
        Q->rear ++ ; // rear 이동
        Q->data[Q->rear] = e; // 데이터 삽입
    }
}

큐에 원소를 삽입하기 전에 포화 상태인지를 확인하고, 포화 상태가 아니라면 원소를 삽입한다.

 

  1. isFull(Q) 함수를 사용하여 큐가 포화 상태인지를 확인한다.
  2. 만약 큐가 포화 상태라면 printf() 함수를 사용하여 "overflow"를 출력하고, 큐의 상태를 출력하는 printQue_R_TO_E(Q) 함수를 호출합니다. 이후 프로그램을 종료하기 위해 exit(1) 함수를 호출한다.
  3. 큐가 포화 상태가 아니라면, Q->rear 변수를 증가시킴으로써 rear를 이동시키고, 해당 위치에 원소를 삽입한다.

이 함수는 큐에 원소를 삽입하는 동작을 수행하며, 포화 상태를 확인하여 오버플로우를 방지한다.

dequeue 함수

// 큐에서 원소를 삭제하는 함수
element dequeue(QueueType *Q){
    // 공백 상태에서 삭제를 시도한 경우 처리
    if (isEmpty(Q)) {
        printf("underflow "); exit(1); 
    } 
    else {
        Q->front ++; // front 이동
        int del = Q->data[Q->front]; // 삭제된 원소 저장

        // Q->data[Q->front] = 0; // 삭제된 위치를 0으로 초기화
        Q->data[Q->front] = ' '; // 삭제된 위치를 0으로 초기화
        return del; // 삭제된 원소 반환
    }
}

해당 함수는 큐에서 원소를 삭제하기 전에 공백 상태인지를 확인하고, 공백 상태가 아니라면 원소를 삭제한다.

 

  1. isEmpty(Q) 함수를 사용하여 큐가 공백 상태인지를 확인한다. 
  2. 만약 큐가 공백 상태라면 printf() 함수를 사용하여 "underflow"를 출력하고, 프로그램을 종료하기 위해 exit(1) 함수를 호출한다.
  3. 큐가 공백 상태가 아니라면, Q->front 변수를 증가시킴으로써 front를 이동시키고, 해당 위치의 원소를 삭제하고 반환한다.
  4. 이때, 삭제된 위치의 값을 공백 문자로 초기화한다.

이 함수는 큐에서 원소를 삭제하는 동작을 수행하며, 공백 상태를 확인하여 언더플로우를 방지한다.

printQue_R_TO_E 함수

void printQue_R_TO_E(QueueType *Q){
     for(int j=0; j<Q->size; j++){
        printf("[%c] ", Q->data[j]); // 배열의 모든 원소 출력
    }
    printf("\n");
}

 

이 함수는 큐의 모든 원소를 출력하는 함수이다. 

 

일반적으로 원형 큐는 front와 rear가 서로를 따라가면서 원형으로 배열을 탐색하므로, front와 rear 사이의 원소만 출력하는 것이 일반적이다. 하지만 여기서는 배열의 모든 원소를 출력하는 것으로 정의되어 있다.

 

이것은 원형 큐의 특성을 무시하고 배열의 모든 원소를 출력하지만 선형 큐의 로직을 위해 이렇게 구현했다.

 

따라서 이 함수를 사용할 때는 주의해야 하며. 만약 이 함수를 사용하여 큐의 상태를 출력할 경우, 원형 큐의 특성을 고려하여 front와 rear 사이의 원소만 출력해야 한다. 올바른 출력은 아래 print 함수를 참조한다.

[X] [Y] [L] [J] [J] [X] [S] [ ] [ ] [ ] 
[X] is dequeded.
[Y] is dequeded.
[L] is dequeded.
[ ] [ ] [ ] [J] [J] [X] [S] [ ] [ ] [ ] 
[ ] [ ] [ ] [J] [J] [X] [S] [X] [N] [K] 
[J] is dequeded.
[ ] [ ] [ ] [ ] [J] [X] [S] [X] [N] [K] 
overflow [ ] [ ] [ ] [ ] [J] [X] [S] [X] [N] [K] 

print 함수

// 큐의 상태를 출력하는 함수
void print(QueueType *Q){
    for(int j=0; j<Q->size; j++){
        printf(" %d", Q->data[j]); // 배열의 모든 원소 출력
    }
    printf("\n");
}

main 함수

[더 보기] 란의 코드 블록을 확인한다.
더보기
int main(){
    int q, n, e; // 큐의 크기, 연산의 개수, 원소
    QueueType Q; // 큐 선언
    Q.size = MAX; // 큐의 크기 설정
    init(&Q); // 큐 초기화
    for (int i = 0; i < Q.size; i++) {Q.data[i] = ' ';}

    srand(time(NULL));

    for (int i = 0; i < 7; i++) enqueue(&Q, rand()% 26 + 65);printQue_R_TO_E(&Q);
    for (int i = 0; i < 3; i++) printf("[%c] is dequeded.\n",dequeue(&Q));printQue_R_TO_E(&Q);
    
    for (int i = 0; i < 3; i++) enqueue(&Q, rand()% 26 + 65);printQue_R_TO_E(&Q);
    for (int i = 0; i < 1; i++) printf("[%c] is dequeded.\n",dequeue(&Q));printQue_R_TO_E(&Q);

    enqueue(&Q, rand()% 26 + 65);printQue_R_TO_E(&Q);
}

전체 코드

[더 보기] 란의 코드 블록을 확인한다.

더보기
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

typedef char element;

#define MAX 10

typedef struct {
    element *data; // 데이터 저장 배열
    int front, rear; // 포인터와 리어 변수
    int size;// 배열의 사이즈
} QueueType;

void print(QueueType *Q);
void printQue_R_TO_E(QueueType *Q);

// 큐 초기화 함수
void init(QueueType *Q){
    Q->data = (char*)malloc(sizeof(char)*Q->size);
    Q->front = Q->rear = -1; // 큐의 front와 rear를 초기화 : -1
    //인덱스가 0부터 배열에 실질적 저장
}

// 큐가 비어있는지 확인하는 함수
int isEmpty(QueueType *Q){return (Q->front == Q->rear);}

// 큐가 꽉 차 있는지 확인하는 함수
int isFull(QueueType *Q){return (Q->rear == Q->size - 1);}

// 큐에 원소를 삽입하는 함수
void enqueue(QueueType *Q, element e){
    // 포화 상태에서 삽입을 시도한 경우 처리
    if (isFull(Q)) {
        printf("overflow "); 
        // print(Q); 
        printQue_R_TO_E(Q);
        exit(1); 
    } 
    else {
        Q->rear ++ ; // rear 이동
        Q->data[Q->rear] = e; // 데이터 삽입
    }
}

// 큐에서 원소를 삭제하는 함수
element dequeue(QueueType *Q){
    // 공백 상태에서 삭제를 시도한 경우 처리
    if (isEmpty(Q)) {
        printf("underflow "); exit(1); 
    } 
    else {
        Q->front ++; // front 이동
        int del = Q->data[Q->front]; // 삭제된 원소 저장

        // Q->data[Q->front] = 0; // 삭제된 위치를 0으로 초기화
        Q->data[Q->front] = ' '; // 삭제된 위치를 0으로 초기화
        return del; // 삭제된 원소 반환
    }
}

// 큐의 상태를 출력하는 함수
void print(QueueType *Q){
    for(int j=0; j<Q->size; j++){
        printf(" %d", Q->data[j]); // 배열의 모든 원소 출력
    }
    printf("\n");
}

void printQue(QueueType *Q){
    int i = Q->front;
    while (i != Q->rear) {
        i = (i + 1) % Q->size;
        printf("[%c] ", Q->data[i]);
    }
    printf("\n");
}

void printQue_R_TO_E(QueueType *Q){
     for(int j=0; j<Q->size; j++){
        printf("[%c] ", Q->data[j]); // 배열의 모든 원소 출력
    }
    printf("\n");
}

// 메인 함수
int main(){
    int q, n, e; // 큐의 크기, 연산의 개수, 원소
    QueueType Q; // 큐 선언
    Q.size = MAX; // 큐의 크기 설정
    init(&Q); // 큐 초기화
    for (int i = 0; i < Q.size; i++) {Q.data[i] = ' ';}

    srand(time(NULL));

    for (int i = 0; i < 7; i++) enqueue(&Q, rand()% 26 + 65);printQue_R_TO_E(&Q);
    for (int i = 0; i < 3; i++) printf("[%c] is dequeded.\n",dequeue(&Q));printQue_R_TO_E(&Q);
    
    for (int i = 0; i < 3; i++) enqueue(&Q, rand()% 26 + 65);printQue_R_TO_E(&Q);
    for (int i = 0; i < 1; i++) printf("[%c] is dequeded.\n",dequeue(&Q));printQue_R_TO_E(&Q);

    enqueue(&Q, rand()% 26 + 65);printQue_R_TO_E(&Q);
}

큐 ADT 구현 [2] 원형 큐

큐의 전단과 후단을 관리하기 위한 2개의 변수가 필요하다.

 

    ▷ front: 첫 번째 요소 하나 앞의 인덱스

    ▷ rear: 마지막 요소의 인덱스

원형 큐

 

 공백상태: front == rear
 포화상태: front % M==(rear+1) % M

 

공백상태와 포화상태를 구별하기 위하여 하나의 공간은 항상 비워둔다.

원형 큐 구현

구조체와 print 및  printQue_R_TO_E 함수의 선언은 동일하다.

init 함수

// 큐 초기화 함수
void init(QueueType *Q){
    Q->data = (char*)malloc(sizeof(char)*Q->size);
    // Q->data = (int*)malloc(sizeof(int)*Q->size);
    Q->front = Q->rear = 0; // 큐의 front와 rear를 초기화
}


이 함수는 큐를 초기화하는 역할을 한다.

 

  1. 큐의 데이터를 저장할 배열을 동적으로 할당한다.
  2. 이때, 배열의 크기는 Q->size로 지정된다. 여기서는 char 타입의 배열로 할당되어 있다.
  3. 큐의 front와 rear를 0으로 초기화한다. 이는 큐가 비어있는 상태임을 나타냅니다.
  4. 일반적으로 front와 rear가 같은 값을 가리키는 경우를 큐의 공백 상태로 정의한다.

이 함수를 통해 큐를 초기화하면, 큐의 배열이 동적으로 할당되고 front와 rear가 초기화되어 비어있는 상태가 된다.

isEmty 함수 / isFull 함수

// 큐가 비어있는지 확인하는 함수
int isEmpty(QueueType *Q){return (Q->front == Q->rear);}

// 큐가 꽉 차 있는지 확인하는 함수
int isFull(QueueType *Q){return (Q->front == (Q->rear + 1) % Q->size);}


이 두 함수는 각각 큐가 비어있는지와 꽉 차 있는지를 확인하는 역할을 한다.

 

isEmpty : 큐가 비어있는지를 확인한다.

 

•  만약 큐가 비어있다면 front와 rear가 같은 위치를 가리키므로 (Q->front == Q->rear)를 반환한다.

 

isFull : 큐가 꽉 차 있는지를 확인한다.

 

  1. 만약 큐가 꽉 차 있다면 (Q->front == (Q->rear + 1) % Q->size)를 반환 합한다.
  2. % 연산자를 사용하여 rear를 한 칸 이동한 위치가 front와 같은지를 확인한다.
  3. 만약 같다면 rear가 front 바로 다음에 위치해 있으므로 큐가 꽉 찬 상태이다.

enqueue 함수

// 큐에 원소를 삽입하는 함수
void enqueue(QueueType *Q, element e){
    // 포화 상태에서 삽입을 시도한 경우 처리
    if (isFull(Q)) {
        printf("overflow "); // overflow 메시지 출력
        printQue_R_TO_E(Q); // 현재 큐의 상태 출력
        exit(1); // 프로그램 종료
    } 
    else {
        Q->rear = (Q->rear + 1) % Q->size; // rear 이동
        Q->data[Q->rear] = e; // 데이터 삽입
    }
}


이 함수는 큐에 원소를 삽입하는 역할을 한다.

 

  1. isFull(Q) 함수를 사용하여 큐가 포화 상태인지를 확인한다.
  2. 만약 큐가 포화 상태라면 printf() 함수를 사용하여 "overflow"를 출력하고, 현재 큐의 상태를 출력하는 printQue_R_TO_E(Q) 함수를 호출한다. 이후 프로그램을 종료하기 위해 exit(1) 함수를 호출한다.
  3. 큐가 포화 상태가 아니라면, Q->rear 변수를 이동시킴으로써 rear를 한 칸 앞으로 이동시키고, 해당 위치에 원소를 삽입한다.
  4. 이때, % 연산자를 사용하여 rear를 한 칸 이동한 위치가 배열의 끝에 도달하면 다시 배열의 처음으로 돌아가도록 처리한다.

이 함수를 통해 큐에 원소를 삽입하며, 포화 상태를 확인하여 오버플로우를 방지한다.

dequeue 함수

// 큐에서 원소를 삭제하는 함수
element dequeue(QueueType *Q){
    // 공백 상태에서 삭제를 시도한 경우 처리
    if (isEmpty(Q)) {
        printf("underflow "); // underflow 메시지 출력
        exit(1); // 프로그램 종료
    } else {
        Q->front = (Q->front + 1) % Q->size; // front 이동
        int del = Q->data[Q->front]; // 삭제된 원소 저장
        Q->data[Q->front] = 0; // 삭제된 위치를 0으로 초기화
        return del; // 삭제된 원소 반환
    }
}

이 함수는 큐에서 원소를 삭제하는 역할을 한다.

 

  1. isEmpty(Q) 함수를 사용하여 큐가 공백 상태인지를 확인한다.
  2. 만약 큐가 공백 상태라면, printf() 함수를 사용하여 "underflow"를 출력하고, 프로그램을 종료하기 위해 exit(1) 함수를 호출한다.
  3. 큐가 공백 상태가 아니라면, front를 한 칸 앞으로 이동시키고, 해당 위치의 원소를 삭제하고 반환한다.
  4. % 연산자를 사용하여 front를 한 칸 이동한 위치가 배열의 끝에 도달하면 다시 배열의 처음으로 돌아가도록 처리한다.
  5. 삭제된 위치의 값을 0으로 초기화한다.

이 함수를 통해 큐에서 원소를 삭제하며, 공백 상태를 확인하여 언더플로우를 방지한다.

main 함수

더보기
// 메인 함수
int main(){
    QueueType Q; // 큐 선언
    Q.size = MAX; // 큐의 크기 설정
    init(&Q); // 큐 초기화
    for (int i = 0; i < Q.size; i++) {Q.data[i] = ' ';}

    srand(time(NULL));

    for (int i = 0; i < 7; i++) enqueue(&Q, rand()% 26 + 65);printQue_R_TO_E(&Q);
    for (int i = 0; i < 3; i++) printf("[%c] is dequeded.\n",dequeue(&Q));printQue_R_TO_E(&Q);
    
    for (int i = 0; i < 3; i++) enqueue(&Q, rand()% 26 + 65);printQue_R_TO_E(&Q);
    for (int i = 0; i < 1; i++) printf("[%c] is dequeded.\n",dequeue(&Q));printQue_R_TO_E(&Q);

    for (int i = 0; i < 3; i++) enqueue(&Q, rand()% 26 + 65);printQue_R_TO_E(&Q);

    enqueue(&Q, rand()% 26 + 65);printQue_R_TO_E(&Q);
}

전체 코드

[더 보기] 란의 코드 블록을 확인한다.

더보기
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

typedef char element;

#define MAX 10

typedef struct {
    element *data; // 데이터 저장 배열
    int front, rear; // 포인터와 리어 변수
    int size;// 배열의 사이즈
} QueueType;

void print(QueueType *Q);
void printQue_R_TO_E(QueueType *Q);

// 큐 초기화 함수
void init(QueueType *Q){
    Q->data = (char*)malloc(sizeof(char)*Q->size);
    // Q->data = (int*)malloc(sizeof(int)*Q->size);
    Q->front = Q->rear = 0; // 큐의 front와 rear를 초기화
}

// 큐가 비어있는지 확인하는 함수
int isEmpty(QueueType *Q){return (Q->front == Q->rear);}

// 큐가 꽉 차 있는지 확인하는 함수
int isFull(QueueType *Q){return (Q->front == (Q->rear + 1) % Q->size);}

// 큐에 원소를 삽입하는 함수
void enqueue(QueueType *Q, element e){
    // 포화 상태에서 삽입을 시도한 경우 처리
    if (isFull(Q)) {
        printf("overflow "); // overflow 메시지 출력
        printQue_R_TO_E(Q); // 현재 큐의 상태 출력
        exit(1); // 프로그램 종료
    } 
    else {
        Q->rear = (Q->rear + 1) % Q->size; // rear 이동
        Q->data[Q->rear] = e; // 데이터 삽입
    }
}

// 큐에서 원소를 삭제하는 함수
element dequeue(QueueType *Q){
    // 공백 상태에서 삭제를 시도한 경우 처리
    if (isEmpty(Q)) {
        printf("underflow "); // underflow 메시지 출력
        exit(1); // 프로그램 종료
    } else {
        Q->front = (Q->front + 1) % Q->size; // front 이동
        int del = Q->data[Q->front]; // 삭제된 원소 저장
        Q->data[Q->front] = 0; // 삭제된 위치를 0으로 초기화
        return del; // 삭제된 원소 반환
    }
}

// 큐의 상태를 출력하는 함수
void print(QueueType *Q){
    for(int j=0; j<Q->size; j++){
        printf(" %d", Q->data[j]); // 배열의 모든 원소 출력
    }printf("\n");
}

void printQue(QueueType *Q){
    int i = Q->front;
    while (i != Q->rear) {
        i = (i + 1) % Q->size;
        printf("%c ", Q->data[i]);
    }
    printf("\n");
}

void printQue_R_TO_E(QueueType *Q){
     for(int j=0; j<Q->size; j++){
        printf("[%c] ", Q->data[j]); // 배열의 모든 원소 출력
    }
    printf("\n");
}

// 메인 함수
int main(){
    QueueType Q; // 큐 선언
    Q.size = MAX; // 큐의 크기 설정
    init(&Q); // 큐 초기화
    for (int i = 0; i < Q.size; i++) {Q.data[i] = ' ';}

    srand(time(NULL));

    for (int i = 0; i < 7; i++) enqueue(&Q, rand()% 26 + 65);printQue_R_TO_E(&Q);
    for (int i = 0; i < 3; i++) printf("[%c] is dequeded.\n",dequeue(&Q));printQue_R_TO_E(&Q);
    
    for (int i = 0; i < 3; i++) enqueue(&Q, rand()% 26 + 65);printQue_R_TO_E(&Q);
    for (int i = 0; i < 1; i++) printf("[%c] is dequeded.\n",dequeue(&Q));printQue_R_TO_E(&Q);

    for (int i = 0; i < 3; i++) enqueue(&Q, rand()% 26 + 65);printQue_R_TO_E(&Q);

    enqueue(&Q, rand()% 26 + 65);printQue_R_TO_E(&Q);
}
728x90
반응형
댓글