Superbly Life & Development

Queue

2019-09-16

큐(Queue)란?

  • 큐는 스택과 마찬가지로 데이터를 일시적으로 쌓아 두기 위한 자료구조이다.
  • 하지만 가장 먼저 넣은 데이터를 가정 먼저 꺼내는 선입선출(FIFO, First In First Out)인 점이 스택과 다르다.
  • 큐에 데이터를 넣는 작업을 인큐(enqueue)라 하고, 데이터를 꺼내는 작업을 디큐(dequeue)라고 한다.
  • 인큐의 복잡도는 O(1)이고 디큐의 복잡도는 O(n)이다 디큐는 데이터를 꺼낼 때 다음 데이터를 앞으로 당기기 때문이다.
  • 또한 데이터를 꺼내는 쪽을 프런트(front)라 하고, 데이터를 넣는 쪽을 리어(rear)라고 한다.

아래 그림은 큐의의 인큐와 디큐를 보여준다.

trace


코드 (배열 활용)

import java.util.Scanner;

public class IntAryQueue {  // int형 큐(링 버퍼(ring buffer)를 사용하지 않음)
    private int max;        // 큐 용량
    private int num;        // 현재 데이터 수
    private int[] queue;    // 큐 본체

    // 생성자
    public IntAryQueue(int capacity) {
        max = capacity;
        num = 0;
        queue = new int[max];
    }

    // 실행 시 예외 : 큐가 비어 있음
    public class EmptyIntQueueException extends RuntimeException {
        public EmptyIntQueueException() {
            throw new EmptyIntQueueException();
        }
    }

    // 실행 시 예외 : 큐가 가득 참
    public class OverflowQUeueException extends RuntimeException {
        public OverflowQUeueException() {
            throw new OverflowQUeueException();
        }
    }

    // 인큐
    public int InQueue(int data) {
        if (num > max)
            throw new OverflowQUeueException();
        return queue[num++] = data;
    }

    // 디큐
    public int DeQueue() {
        if (num <= 0)
            throw new EmptyIntQueueException();

        int result = queue[0];
        for (int i = 0; i < num - 1; i++)
            queue[i] = queue[i + 1];
        num--;

        return result;
    }

    // 큐에서 데이터를 피크(머리 데이터를 살펴봄)
    public int peek() throws EmptyIntQueueException {
        if (num <= 0)
            throw new EmptyIntQueueException(); // 큐가 비어 있음
        return queue[num - 1];
    }

    // 큐에서 x를 검색하여 index(찾지 못하면 -1)를 반환
    public int indexOf(int x) {
        for (int i = 0; i < num; i++)
            if (queue[i] == x) // 검색성공
                return i;
        return -1; // 검색실패
    }

    // 큐를 비움
    public void clear() {
        num = 0;
    }

    // 큐의 용량을 반환
    public int capacity() {
        return max;
    }

    // 큐에 쌓인 데이터 수를 반환
    public int size() {
        return num;
    }

    // 큐가 비어 있는가?
    public boolean isEmpty() {
        return num <= 0;
    }

    // 큐가 가득 찼는가?
    public boolean isFull() {
        return num >= max;
    }

    // 큐 안의 데이터를 머리→꼬리의 차례로 출력함
    public void dump() {
        if (num <= 0)
            System.out.println("큐가 비었습니다.");
        else {
            for (int i = 0; i < num; i++)
                System.out.print(queue[i] + " ");
            System.out.println();
        }
    }
}


큐는 어디에 사용될까?

  1. 운영체제 작업 스케쥴링
  2. 대기행렬 처리
  3. 인쇄작업 대기목록

링 버퍼로 큐 만들기

  • 이번에는 배열 요소를 앞쪽으로 옮기지 않는 링 버퍼 형태의 큐를 만들어 보자.
  • 링 버퍼는 아래 그림과 같이 배열의 처음과 끝이 연결되어있는 자료구조 이다.
  • 논리적으로 어떤 요소가 첫 번째 요소이고 어떤 요소가 마지막 요소인지 식별하기 위한 변수가 프런트(front)리어(rear)이다.

프런트(front) : 맨 처음 요소의 인덱스 리어(rear) : 맨 끝 요소의 하나 뒤의 인덱스 (다음 요소를 인큐할 위치를 미리 지정)

trace


코드 (링 버퍼로 큐 구현)

public class Queue<E> { // 링 버퍼 큐 구현
    private int max;    // 큐의 용량
    private int num;    // 현재 데이터 수
    private int front;  // 프런트 요소 커서
    private int rear;   // 리어 요소 커서
    private E[] queue;  // 큐의 본체

    public static class OverflowQueueException extends RuntimeException{
        public OverflowQueueException(){
            throw new OverflowQueueException();
        }
    }

    public static class EmptyQueueException extends RuntimeException{
        public EmptyQueueException(){
            throw new EmptyQueueException();
        }
    }

    // 생성자
    public Queue(int capacity){
        num = front = rear = 0;
        E[] queue = (E[]) new Object[capacity];
    }

    // 인큐
    public E inque(E data) throws OverflowQueueException{
        if(num >= max)
            throw new OverflowQueueException();

        E print = queue[rear++] = data;
        num++;

        if(rear == max)     // rear와 max가 같아지면 rear 값이 배열의 없는 공간을 가르키게 된다.
            rear = 0;       // 그러므로 0으로 초기화

        return print;
    }

    // 디큐
    public E deque() throws EmptyQueueException{
        if(num <= 0)
            throw new EmptyQueueException();

        E print = queue[front++];
        num--;

        if(front == max)    // 인큐와 같은 인덱스 초과 문제를 막기 위함
            front = 0;

        return print;
    }

    // 큐에서 데이터를 피크(프런트 데이터를 들여다 봄)
    public E peek() throws EmptyQueueException{
        if(num <= 0)
            throw new EmptyQueueException();
        return queue[front];
    }

    // 큐에서 data를 검색하여 인덱스(찾지 못하면 -1)를 반환
    public int indexOf(E data){
        for (int i = 0; i < num; i++) {
            int idx = (front + i) % max;   // 스캔의 시작은 배열의 첫 요소가 아니라 큐의 첫 요소, 즉 프런트이기 때문이다.
            if(queue[idx] == data)
                return idx;
        }
        return -1;
    }

    // 검색
    public int search(E data){
        int number = 0;
        for (int i = 0; i < num; i++) {
            int idx = (front + i) % max;
            number++;
            if(queue[idx] == data)
                return number;
        }
        return 0;
    }

    // 큐를 비움
    public void clear() {
        num = 0;
    }

    // 큐의 용량을 반환
    public int capacity() {
        return max;
    }

    // 큐에 쌓인 데이터 수를 반환
    public int size() {
        return num;
    }

    // 큐가 비어 있는가?
    public boolean isEmpty() {
        return num <= 0;
    }

    // 큐가 가득 찼는가?
    public boolean isFull() {
        return num >= max;
    }

    // 큐 안의 데이터를 머리→꼬리의 차례로 출력함
    public void dump() {
        if (num <= 0)
            System.out.println("큐가 비었습니다.");
        else {
            for (int i = 0; i < num; i++)
                System.out.print(queue[(front + i) % max] + " ");
            System.out.println();
        }
    }
}

리뷰

개인적으로 코드에서 자세히 살펴봐야 할 부분을 다시 리뷰한다.

인큐

// 인큐
public E inque(E data) throws OverflowQueueException{
    if(num >= max)
        throw new OverflowQueueException();

    E print = queue[rear++] = data;
    num++;

    if(rear == max)     // rear와 max가 같아지면 rear 값이 배열의 없는 공간을 가르키게 된다.
        rear = 0;       // 그러므로 0으로 초기화

    return print;
}
  1. 인큐에서 num이 max보다 크다면 예외를 던진다.

  2. 그렇지 않다면 큐에 데이터를 집어 넣고 rear값을 증가시킨다. (다음 인큐할 곳을 가르키기 위해)
  3. if(rear == max) rear = 0; 이 부분이 중요하다고 생각한다.
    • max값이 12이며 만약 인큐하기 전의 rear가 11이라고 가정해보자.
    • 인큐 메서드를 수행한 다음에는 rear의 값이 12가 되면서 max값과 같아지는 문제가 발생한다.
    • rear 값이 max와 같아지면 실제 배열에는 없는 공간인 queue[12]를 가리키게 됨
    • 위와 같은 경우를 방지하기 위해 rear == max 이면 rear 를 0으로 초기화 해준다.

디큐

// 디큐
public E deque() throws EmptyQueueException{
    if(num <= 0)
        throw new EmptyQueueException();

    E print = queue[front++];
    num--;

    if(front == max)    // 인큐와 같은 인덱스 초과 문제를 막기 위함
        front = 0;

    return print;
}
  1. num이 0보다 작으면 예외를 던진다.
  2. front값을 증가시킨다.
  3. num값을 1 감소시킨다.
  4. if(front == num) front = 0;
    • 여기서 인큐과 마찬가지로 인덱스 초과 문제가 발생한다.

검색 메서드 indexOf

// 큐에서 data를 검색하여 인덱스(찾지 못하면 -1)를 반환
public int indexOf(E data){
    for (int i = 0; i < num; i++) {
        int idx = (front + i) % max;   // 스캔의 시작은 배열의 첫 요소가 아니라 큐의 첫 요소, 즉 프런트이기 때문이다.
        if(queue[idx] == data)
            return idx;
    }
    return -1;
}

아래의 그림으로 간단히 설명한다.

trace


버퍼 링은 어디에 사용될까?

  1. 오래된 데이터를 버리는 용도
    • 가장 최근에 들어온 데이터 n개만 저장하고 오래된 데이터는 버리는 용도로 사용한다.

참고


이전 Stack

다음 Jquery

Comments

Content