자료구조

큐(Queue)

땅콩콩 2022. 7. 16. 16:33
큐란?

데이터를 일시적으로 쌓아놓은 자료구조이다. 

데이터의 입출력순서는 선입선출(먼저 들어온 것이 먼저 나감)이다.

큐에 데이터를 넣는 작업을 인큐(enqueue), 꺼내는 작업을 디큐(dequeue)라고 한다.

 

즉 어떤 데이터를 인큐하면 큐의 맨 뒤에 데이터를 추가하고,

데이터를 디큐하면 큐의 맨 앞 데이터를 제거한다음 한칸씩 앞으로 당긴다.

근데 이렇게 디큐할때의 복잡도는 O(n)으로, 데이터를 하나 꺼낼때마다 이런 처리를 한다면 효율이 매우 떨어진다.

 

이런 문제점은 링버퍼를 사용하여 큐를 구현하면 개선할 수 있다.

프런트와 리어 값을 업데이트하며 인큐와 디큐를 수행하게 되기 때문이다.

[ O(1) ]

 

다음은 링버퍼로 큐를 구현하는 코드이다.

구간별 설명은 주석으로 대신한다!

 

import java.util.*;

public class Main {

    static class IntQueue{
        private int max; //큐의 용량
        private int front; //첫번째 요소 커서
        private int rear; //마지막 요소 커서(마지막 요소 하나 뒤의 인덱스)
        private int num; //현재 데이터수
        private int[] que; //큐 본체

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

        //실핼시 예외: 큐가 가득참
        public class OverflowIntQueueException extends RuntimeException{
            public OverflowIntQueueException(){}
        }

        //생성자
        public IntQueue(int capacity){
            num = front = rear = 0;
            max = capacity;
            try{
                que = new int[max];
            }catch(OutOfMemoryError e){
                max = 0;
            }
        }

        //큐에 데이터를 인큐
        public int enque(int x) throws OverflowIntQueueException{
            if(num>=max)
                throw new OverflowIntQueueException();
            que[rear++] = x;
            num++;
            if(rear==max)
                rear = 0;
            return x;
        }

        //큐에서 데이터를 디큐
        public int deque() throws EmptyIntQueueException {
            if(num<=0)
                throw new EmptyIntQueueException();
            int x = que[front++];
            num--;
            if(front==max)
                front=0;
            return x;
        }

        //큐에서 데이터를 피크(front 데이터 엿보기)
        public int peek() throws EmptyIntQueueException{
            if(num<=0)
                throw new EmptyIntQueueException();
            return que[front];
        }

        //큐에서 x를 검색하여 인덱스 반환. 없으면 -1 반환.
        //스캔의 시작은 배열의 첫 요소가 아니라 큐의 첫 요소!(front) 따라서 인덱스가 (i+front)%max 이다.
        public int indexOf(int x){
            for (int i = 0; i < num; i++) {
                int idx = (i+front) % max;
                if(que[idx]==x)
                    return idx;
            }
            return -1;
        }

        //큐를 비움
        public void clear(){
            num = front = rear = 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.println(que[(i+front) % max]+" ");
                }
                System.out.println();
            }
        }


    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        IntQueue q = new IntQueue(64);

        while (true){
            System.out.println("현재 데이터 수:" + q.size() + " / "
                    + q.capacity());
            System.out.print("(1)인큐 (2)디큐 (3)피크 " +
                    "(4)덤프 (0)종료:");

            int menu = scanner.nextInt();
            if (menu == 0) break;

            int x;
            switch (menu){
                case 1:
                    System.out.print("데이터: ");
                    x = scanner.nextInt();
                    try {
                        q.enque(x);
                    }catch (IntQueue.OverflowIntQueueException e){
                        System.out.println("큐가 가득 찼습니다.");
                    }
                    break;

                case 2:
                    try {
                        x = q.deque();
                        System.out.println("디큐한 데이터는 "+x+"입니다.");
                    }catch (IntQueue.EmptyIntQueueException e){
                        System.out.println("큐가 비어 있습니다.");
                    }
                    break;

                case 3:
                    try {
                        x = q.peek();
                        System.out.println("피크한 데이터는 "+x+"입니다.");
                    }catch (IntQueue.EmptyIntQueueException e){
                        System.out.println("큐가 비어 있습니다.");
                    }
                    break;

                case 4:
                    q.dump();
                    break;
            }
        }
    }
}

 

링버퍼의 활용

링버퍼는 오래된 데이터를 버리는 용도로 사용할 수 있다.

 

예를 들어 배열의 길이가 10인 배열에 \1~12까지 순서대로 12개의 데이터가 입력된다면, 앞의 두개를 뒤의 두개가 덮어써서 총 3~12까지의 데이터만 배열에 남기는 식이다.

 

다음은 위의 예를 링버퍼를 사용해서 구현한 예시이다.

 

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        final int N = 10;
        int[] a = new int[N];
        int cnt = 0;
        int retry;

        System.out.println("정수를 입력하세요.");

        do {
            System.out.printf("%d번째 정수:", cnt + 1);
            a[cnt++ % N] = scanner.nextInt();

            System.out.print("계속 할까요? (예.1/아니오.0):");
            retry = scanner.nextInt();
        } while (retry == 1);

        int i = cnt - N;
        if (i < 0) i = 0;

        for ( ; i < cnt; i++)
            System.out.printf("%2d번째의 정수 = %d\n", i + 1, a[i % N]);
    }
}

'자료구조' 카테고리의 다른 글

정렬 알고리즘  (0) 2023.10.17
재귀 알고리즘  (0) 2022.07.17
스택(Stack)  (0) 2022.07.16
배열검색 (이진검색)  (0) 2022.07.15
배열 검색 (선형검색, 보초법)  (0) 2022.07.15