[Java 심화] Collection & Map - 2. Queue & Deque (PriorityQueue, ArrayDeque, LinkedList)

Kyung Jae, Cheong·2024년 10월 12일
0
post-thumbnail

Collection & Map - 2. Queue & Deque

  • 이번 시리즈에서는 Java의 Collection과 Map에 대해 소개합니다.
    • 프레임워크의 기반이 되는 자료구조에 대한 개념은 따로 시리즈로 작성할 것이고, 본 시리즈에서는 Java에서 제공하는 자료구조들과 그에 따른 메서드들을 정리할 것입니다.

0. Java의 Collection Framework와 Map Interface

  • Java에서는 데이터 구조와 알고리즘을 지원하는 강력한 표준 라이브러리를 제공하며, 이 중 Collection Framework와 Map은 데이터 관리와 처리의 핵심적인 역할을 합니다.

  • 위 이미지는 주로 쓰이는 Collection Framework와 Map Interface의 상속 및 구현 관계를 제가 직접 그려본 것입니다.
    • 회색으로 표시된 Vector, Stack, Hashtable은 Java 초창기에 만들어져서 하위 버전 호환을 위해 남겨진 Legacy Class들입니다.
      • 구조적인 문제들이 있어서 현재는 더이상 업데이트되지 않고 거의 쓰이지 않기 때문에 본 시리즈에서도 따로 정리하진 않을 예정입니다.
    • Collection Framework
      • List 구현체 : ArrayList, LinkedList
      • Queue 구현체 : PriorityQueue, ArrayDeque, LinkedList
      • Deque 구현체 : ArrayDeque, LinkedList
      • Set 구현체 : HashSet, LinkedHashSet, TreeSet
    • Map Interface 구현체 : HashMap, LinkedHashMap, TreeMap
  • 이번 포스팅에서는 Collection Framework의 하위 인터페이스인 QueueDeque 인터페이스를 다루고, 두 인터페이스의 구현체 클래스인 ArrayDeque, LinkedList, PriorityQueue에 대해서 다룰 것입니다.
  • 우선 가장 상위 클래스인 Queue 인터페이스를 살펴보도록 하겠습니다.

1. Queue Interface - 개요

  • QueueFIFO(First-In, First-Out) 구조를 따르는 컬렉션으로, 먼저 삽입된 요소가 먼저 삭제되는 방식으로 동작합니다.
    • Queue대기열, 작업 큐, 메시지 큐순서에 맞춰 처리해야 하는 작업에서 주로 사용됩니다.
    • 데이터의 삽입과 제거가 주된 연산이므로, 시작 부분에서 요소를 제거하고 끝 부분에 요소를 삽입하는 데 최적화된 구조입니다.

1.1. Queue의 주요 특징

  • FIFO 구조: 먼저 들어온 요소가 먼저 처리되는 구조로, 요소의 삽입과 삭제 순서가 일관되게 유지됩니다.
    • 다만 PriorityQueue에서는 예외적으로 삭제시 삽입 순서에 관계없이 지정된 우선순위가 가장 높거나 낮은 데이터가 먼저 처리됩니다.
  • 추가와 제거: offer() 또는 add()를 사용해 큐의 끝에 요소를 추가하고, poll() 또는 remove()를 사용해 큐의 앞에서 요소를 제거합니다.
    • 참고로 Collection에서 상속 받은 add()void 메서드이고, Queue 고유 메서드인 offer()boolean을 반환하는 메서드입니다.
  • 큐가 비어있을 때 처리: poll()은 큐가 비어있을 경우 null을 반환하며, remove()는 예외를 발생시킵니다.

1.2. Queue 주요 메서드

  • 상위 인터페이스를 포함한 더 자세한 메서드들은 아래쪽(3번 파트)에서 다룰 예정이고, 아래 표는 Queue의 고유 메서드들을 간략하게 정리한 것입니다.
메서드설명반환값
offer(E e)큐의 끝에 요소를 추가boolean
poll()큐의 앞에서 요소를 제거하고 반환E (null 반환 가능)
peek()큐의 앞에 있는 요소를 조회 (제거하지 않음)E (null 반환 가능)
remove()큐의 앞에서 요소를 제거하고 반환E (예외 발생 가능)
element()큐의 앞에 있는 요소를 조회 (제거하지 않음)E (예외 발생 가능)

1.3. Queue의 주요 구현체

  • Queue에서는 하위 인터페이스인 Deque의 구현체들을 포함하여 대표적으로는 3개의 구현체가 있습니다.
    • LinkedList : 이중 연결리스트 기반의 Deque구현체이고, Queue로 의존성을 주입하여 사용할 수 있습니다.
    • ArrayDeque : 배열 기반의 Deque구현체이고, Queue로 의존성을 주입하여 사용할 수 있습니다.
    • PriorityQueue : Heap 자료구조 기반의 Queue를 직접 구현한 구현체입니다. 위 두 구현체와 비교하여 동작 방식의 차이가 있습니다. (우선 순위가 가장 높은 데이터가 먼저 출력되는 방식입니다.)

2. Deque Interface - 개요

  • DequeDouble Ended Queue(양방향 큐)의 약자로, 양쪽 끝에서 요소를 추가하고 제거할 수 있는 자료구조입니다.
    • DequeFIFOLIFO(Last-In, First-Out) 두 가지 동작을 모두 지원하기 때문에, Queue뿐만 아니라 Stack으로도 활용할 수 있습니다.
    • Java에서 DequeStack의 대안으로 사용되며, 양 끝에서의 삽입과 삭제가 필요한 작업에 최적화된 자료구조입니다.

2.1. Deque의 주요 특징

  • 양방향 삽입/삭제: Deque는 앞쪽과 뒤쪽 모두에서 요소를 삽입하거나 제거할 수 있습니다.
    • addFirst(), removeFirst()는 앞쪽에서, addLast(), removeLast()는 뒤쪽에서 요소를 처리합니다.
  • 스택과 큐 모두로 활용 가능: Deque스택처럼 LIFO 방식으로 사용할 수도 있고, 큐처럼 FIFO 방식으로 사용할 수도 있습니다.
    • 이를 통해 Deque는 유연한 자료구조로, 다양한 상황에서 적합한 방식으로 사용될 수 있습니다.
  • Null 값 금지: Dequenull 요소를 허용하지 않으며, null 값을 삽입하려고 하면 NullPointerException이 발생합니다.

2.2. Deque 주요 메서드

  • Deque 인터페이스는 Queue 인터페이스를 상속받기 때문에 Queue의 기능을 확장하여 양쪽에서의 삽입 및 삭제를 위한 메서드를 추가로 제공합니다.

추가 관련 Deque 고유 메서드

메서드설명반환값
addFirst(E e)앞쪽에 요소를 추가void
addLast(E e)뒤쪽에 요소를 추가void
offerFirst(E e)앞쪽에 요소를 추가 (실패 시 false 반환)boolean
offerLast(E e)뒤쪽에 요소를 추가 (실패 시 false 반환)boolean
push(E e)앞쪽에 요소를 추가 (addFirst와 동일)void

삭제 관련 Deque 고유 메서드

메서드설명반환값
removeFirst()앞쪽에서 요소를 제거하고 반환E (예외 발생 가능)
removeLast()뒤쪽에서 요소를 제거하고 반환E (예외 발생 가능)
pollFirst()앞쪽에서 요소를 제거하고 반환 (비어있으면 null 반환)E (null 반환 가능)
pollLast()뒤쪽에서 요소를 제거하고 반환 (비어있으면 null 반환)E (null 반환 가능)
pop()앞쪽에서 요소를 제거하고 반환 (removeFirst와 동일)E (예외 발생 가능)

조회 관련 Deque 고유 메서드

메서드설명반환값
getFirst()앞쪽의 요소를 반환 (제거하지 않음)E (예외 발생 가능)
getLast()뒤쪽의 요소를 반환 (제거하지 않음)E (예외 발생 가능)
peekFirst()앞쪽의 요소를 조회 (제거하지 않음, 비어있으면 null 반환)E (null 반환 가능)
peekLast()뒤쪽의 요소를 조회 (제거하지 않음, 비어있으면 null 반환)E (null 반환 가능)

2.3. Deque의 활용

  • Deque는 스택과 큐의 두 가지 역할을 모두 수행할 수 있기 때문에, 다양한 상황에서 활용될 수 있습니다.
    • 큐처럼 사용: FIFO 방식으로 데이터를 처리해야 할 경우, addLast()removeFirst() 메서드를 사용하여 Deque를 큐로 사용할 수 있습니다.
    • 스택처럼 사용: LIFO 방식으로 데이터를 처리해야 할 경우, addFirst()removeFirst() 메서드를 사용하여 Deque를 스택으로 사용할 수 있습니다.
      • 참고로 Dequepush()pop() 메서드도 함께 포함되어 있습니다.
import java.util.Deque;
import java.util.LinkedList;

public class DequeExample {
    public static void main(String[] args) {
        Deque<String> deque = new LinkedList<>();
        
        // 1. 스택처럼 사용 (LIFO)
        deque.addFirst("A");
        deque.addFirst("B");
        deque.addFirst("C");
        
        System.out.println("스택처럼 사용 (LIFO):");
        while (!deque.isEmpty()) {
            System.out.println(deque.removeFirst());
        }
        
        // 2. 큐처럼 사용 (FIFO)
        deque.addLast("1");
        deque.addLast("2");
        deque.addLast("3");
        
        System.out.println("큐처럼 사용 (FIFO):");
        while (!deque.isEmpty()) {
            System.out.println(deque.removeFirst());
        }
    }
}

2.4. Deque의 주요 구현체

  • Deque는 인터페이스로, LinkedListArrayDeque가 대표적인 구현체입니다. 각 구현체는 내부 구조와 성능 특성에 차이가 있습니다.
    • LinkedList: 이중 연결 리스트 기반의 Deque 구현체로, 양쪽 끝에서의 삽입/삭제 성능이 뛰어납니다.
    • ArrayDeque: 배열 기반의 Deque 구현체로, LinkedList보다 빠른 성능을 보이는 경우가 많고 메모리 오버헤드가 적습니다. 스택 또는 큐로 사용할 때 매우 효율적입니다.

3. Queue & Deque - 주요 메서드

  • 주요 메서드는 상당히 많기 때문에 상위 클래스 및 인터페이스에서 부터 하위 인터페이스 순서로 메서드를 정리하고자 합니다.

3.1. Object & Iterable Interface 메서드

Object 메서드

  • Object 클래스는 모든 클래스의 최상위 클래스이므로, QueueDeque 구현체들도 이를 상속받습니다.
메서드설명반환값
equals(Object obj)두 객체가 동일한지 비교boolean
hashCode()객체의 해시 코드를 반환int
toString()객체의 문자열 표현 반환String

Iterable 메서드

  • Iterable 인터페이스는 모든 컬렉션이 구현해야 하며, 반복자를 생성하는 메서드가 포함됩니다.
메서드설명반환값
iterator()컬렉션의 요소를 순차적으로 탐색할 수 있는 Iterator 반환Iterator<T>
forEach(Consumer<? super T> action)각 요소에 대해 주어진 동작을 수행void
spliterator()요소의 분할 가능한 반복자를 반환Spliterator<T>

3.2. Collection Interface 메서드

  • Collection 인터페이스는 List, Set, Queue, Deque 등 모든 컬렉션의 공통 메서드를 정의합니다.
메서드설명반환값
add(T e)요소 추가boolean
addAll(Collection<? extends T> c)주어진 컬렉션의 모든 요소를 추가boolean
remove(Object o)지정된 요소 삭제boolean
removeAll(Collection<?> c)주어진 컬렉션에 있는 모든 요소를 삭제boolean
retainAll(Collection<?> c)주어진 컬렉션에 있는 요소들만 유지boolean
size()컬렉션의 크기 반환int
isEmpty()컬렉션이 비어있는지 여부 확인boolean
contains(Object o)지정된 요소가 있는지 확인boolean
containsAll(Collection<?> c)주어진 컬렉션의 모든 요소가 포함되어 있는지 확인boolean
clear()컬렉션의 모든 요소 삭제void
toArray()컬렉션의 요소를 배열로 반환Object[] or T[]

3.3. Queue Interface 메서드

  • Queue 인터페이스는 Collection 인터페이스를 상속받아, 큐의 특성에 맞는 추가적인 메서드를 정의합니다.
메서드설명반환값
offer(E e)큐의 끝에 요소를 추가boolean
poll()큐의 앞에서 요소를 제거하고 반환E (null 반환 가능)
peek()큐의 앞에 있는 요소를 조회 (제거하지 않음)E (null 반환 가능)
remove()큐의 앞에서 요소를 제거하고 반환E (예외 발생 가능)
element()큐의 앞에 있는 요소를 조회 (제거하지 않음)E (예외 발생 가능)

3.4. Deque Interface 메서드

  • DequeQueue를 확장하여 양방향 큐의 삽입/삭제 기능을 추가로 정의합니다.

추가 관련 Deque 고유 메서드

메서드설명반환값
addFirst(E e)앞쪽에 요소를 추가void
addLast(E e)뒤쪽에 요소를 추가void
offerFirst(E e)앞쪽에 요소를 추가 (실패 시 false 반환)boolean
offerLast(E e)뒤쪽에 요소를 추가 (실패 시 false 반환)boolean
push(E e)앞쪽에 요소를 추가 (addFirst와 동일)void

삭제 관련 Deque 고유 메서드

메서드설명반환값
removeFirst()앞쪽에서 요소를 제거하고 반환E (예외 발생 가능)
removeLast()뒤쪽에서 요소를 제거하고 반환E (예외 발생 가능)
pollFirst()앞쪽에서 요소를 제거하고 반환 (비어있으면 null 반환)E (null 반환 가능)
pollLast()뒤쪽에서 요소를 제거하고 반환 (비어있으면 null 반환)E (null 반환 가능)
pop()앞쪽에서 요소를 제거하고 반환 (removeFirst와 동일)E (예외 발생 가능)

조회 관련 Deque 고유 메서드

메서드설명반환값
getFirst()앞쪽의 요소를 반환 (제거하지 않음)E (예외 발생 가능)
getLast()뒤쪽의 요소를 반환 (제거하지 않음)E (예외 발생 가능)
peekFirst()앞쪽의 요소를 조회 (제거하지 않음, 비어있으면 null 반환)E (null 반환 가능)
peekLast()뒤쪽의 요소를 조회 (제거하지 않음, 비어있으면 null 반환)E (null 반환 가능)

4. Queue 구현체 - PriorityQueue

4-1. PriorityQueue란?

  • PriorityQueueQueue 인터페이스를 구현한 클래스이며, 일반적인 큐와 달리 우선순위에 따라 요소가 처리되는 자료구조입니다.
    • 기본적으로 최소 우선순위 큐(Min-Heap) 구조로 동작하며, 작은 값이 높은 우선순위를 가집니다.
    • 기본적으로는 자연 순서를 따르며(숫자의 경우 오름차순, 알파벳의 경우 사전순), 특정 Comparator를 제공하여 사용자 정의 정렬 순서로 사용할 수 있습니다.
    • 우선순위 큐의 내부 구현은 힙(Heap) 자료구조를 기반으로 하여, 요소의 삽입과 삭제가 이진 트리 형태로 관리됩니다.
      • 참고로 힙(Heap) 자료구조는 메모리 Heap 영역과는 어원만 같을뿐 전혀 관련이 없는 자료구조에서의 용어입니다.

PriorityQueue 주요 특징

  • 우선순위에 따라 요소가 처리됨: PriorityQueue는 가장 먼저 추가된 요소가 아니라, 우선순위가 높은 요소가 먼저 처리됩니다.
    • 즉, FIFO가 아닌 우선순위에 따라 처리되는 점이 일반 큐와의 차이점입니다.
  • 힙 구조 기반: 내부적으로 이진 힙(Heap) 구조를 사용하여 요소를 정렬하고 관리합니다.
    • 이로 인해 삽입과 삭제 연산의 시간 복잡도는 O(log n) 입니다.
  • 자연 순서 또는 사용자 정의 정렬: 기본적으로 요소들은 자연 순서(Comparable 구현을 따름)대로 정렬되지만, Comparator를 사용하여 사용자 정의 정렬 방식을 지정할 수도 있습니다.
  • 동등한 우선순위: 동일한 우선순위를 가진 요소들은 순서가 보장되지 않으므로, 동일한 우선순위가 여러 개일 경우 결과는 예측할 수 없습니다.

4-2. PriorityQueue 사용 예시

import java.util.PriorityQueue;

public class PriorityQueueExample {
    public static void main(String[] args) {
        // 1. 기본 PriorityQueue 생성 (최소 우선순위 큐)
        PriorityQueue<Integer> pq = new PriorityQueue<>();

        // 2. 요소 추가
        pq.offer(10);
        pq.offer(20);
        pq.offer(15);

        // 3. 우선순위가 가장 높은 요소 출력 (최소값)
        System.out.println("우선순위가 가장 높은 요소: " + pq.peek());  // 10

        // 4. 요소 제거
        System.out.println("제거된 요소: " + pq.poll());  // 10
        System.out.println("우선순위가 가장 높은 요소: " + pq.peek());  // 15

        // 5. 전체 출력
        System.out.println("남은 요소들: " + pq);  // [15, 20]
    }
}

우선순위 사용자 정의 (Comparator 사용)

  • 기본적으로는 자연 순서(오름차순)에 따라 우선순위가 정해지지만, Comparator를 통해 정렬 기준을 직접 정의할 수 있습니다.
    • 예를 들어, 최대 우선순위 큐(큰 값이 높은 우선순위를 가짐)를 만들고 싶을 때는 다음과 같이 작성합니다.
import java.util.Comparator;
import java.util.PriorityQueue;

public class MaxPriorityQueueExample {
    public static void main(String[] args) {
        // 1. 최대 우선순위 큐 생성 (Comparator 사용)
        PriorityQueue<Integer> maxPQ = new PriorityQueue<>(Comparator.reverseOrder());

        // 2. 요소 추가
        maxPQ.offer(10);
        maxPQ.offer(20);
        maxPQ.offer(15);

        // 3. 우선순위가 가장 높은 요소 출력 (최대값)
        System.out.println("우선순위가 가장 높은 요소: " + maxPQ.peek());  // 20

        // 4. 요소 제거
        System.out.println("제거된 요소: " + maxPQ.poll());  // 20
        System.out.println("우선순위가 가장 높은 요소: " + maxPQ.peek());  // 15
    }
}

4-3. PriorityQueue의 동작 방식 (Heap)

  • PriorityQueue힙(Heap) 자료구조를 기반으로 동작합니다. 힙은 완전 이진 트리의 일종으로, 최소 힙 또는 최대 힙의 구조로 유지됩니다.
    • 최소 힙: 부모 노드가 자식 노드보다 작거나 같은 값을 가짐. PriorityQueue의 기본 구현은 최소 힙입니다.
    • 최대 힙: 부모 노드가 자식 노드보다 크거나 같은 값을 가짐. Comparator를 사용해 최대 힙으로 만들 수 있습니다.
  • 요소가 삽입될 때는 힙 성질을 유지하도록 이진 트리 구조에 따라 위치가 조정되고, 요소가 삭제될 때는 루트 노드에서 우선순위가 가장 높은 요소가 제거됩니다.
    • 삽입/삭제 시에 새로운 요소가 삽입되거나 제거될 때마다, 부모/자식 관계를 확인하며 위치를 조정해 힙 구조를 유지합니다.

4-4. PriorityQueue의 주의점 및 사용 팁

  • FIFO가 아님: PriorityQueue는 일반적인 큐처럼 삽입 순서가 아닌 우선순위에 따라 요소가 처리됩니다. 따라서 순서를 보장해야 하는 작업에는 적합하지 않습니다.
  • 동등한 우선순위: 동일한 우선순위를 가지는 요소들의 처리 순서는 보장되지 않습니다.
  • Null 값 금지: PriorityQueuenull 요소를 허용하지 않으며, null 값을 삽입하려고 하면 NullPointerException이 발생합니다.
  • 요소 순서 출력: PriorityQueue는 내부적으로 힙을 사용하기 때문에, 큐의 전체 요소를 출력할 때는 순서가 보장되지 않습니다. 요소들은 정렬되지 않은 상태로 출력될 수 있습니다.
    • 정확한 우선순위를 확인하려면, poll()로 요소를 하나씩 꺼내며 확인하는 것이 필요합니다.

5-1. Deque 구현체 - LinkedList

5-1.1. LinkedList의 Deque 기능

  • LinkedListDeque 인터페이스를 구현한 대표적인 자료구조로, 양방향에서 삽입과 삭제가 가능합니다.
    • 이중 연결 리스트(Doubly Linked List)를 기반으로 하여, 앞과 뒤 양쪽 끝에서 모두 빠른 삽입과 삭제가 가능합니다.
    • Deque 인터페이스를 통해 큐(Queue)스택(Stack) 두 가지 방식 모두로 활용할 수 있습니다.

Deque의 주요 동작 방식

  • FIFO 방식으로 큐처럼 사용: addLast()orofferLast()removeFirst()orpollFirst()를 통해 첫 번째 요소를 삭제하고 마지막에 요소를 추가하는 방식으로, 일반적인 큐처럼 사용할 수 있습니다.
    • 큐 방식에서는 offerLast()pollFirst()를 더 권장합니다.
  • LIFO 방식으로 스택처럼 사용: addFirst()orpush()removeFirst()orpop()을 통해 마지막에 삽입된 요소를 먼저 제거하는 방식으로, 스택처럼 사용할 수 있습니다. (어떤걸 써도 무방하긴 합니다)

5-1.2. LinkedList를 이용한 Deque 사용 예시

import java.util.Deque;  
import java.util.LinkedList;  

public class DequeLinkedListExample {  
    public static void main(String[] args) {  
        // LinkedList로 Deque 생성  
        Deque<String> deque = new LinkedList<>();  

        // 1. 큐처럼 사용 (FIFO)  
        deque.addLast("A");  
        deque.addLast("B");  
        deque.addLast("C");  
        
        System.out.println("큐처럼 사용 (FIFO):");  
        while (!deque.isEmpty()) {  
            System.out.println(deque.removeFirst());  
        }  

        // 2. 스택처럼 사용 (LIFO)  
        deque.addFirst("1");  
        deque.addFirst("2");  
        deque.addFirst("3");  
        
        System.out.println("스택처럼 사용 (LIFO):");  
        while (!deque.isEmpty()) {  
            System.out.println(deque.removeFirst());  
        }  
    }  
}
  • 위 예제에서는 LinkedListDeque로 의존성을 주입하여 사용해서 큐(FIFO)와 스택(LIFO) 동작을 모두 구현했습니다.

5-2. Deque 구현체 - ArrayDeque

5-2.1. ArrayDeque란?

  • ArrayDeque는 배열 기반의 Deque 구현체로, 스택과 큐의 기능을 모두 효율적으로 지원합니다.
    • LinkedList와 비교하여 더 빠르고 메모리 오버헤드가 적은 특징을 가지고 있으며, 일반적인 스택과 큐의 대안으로 자주 사용됩니다.
    • Resizable 배열을 사용해 동적 크기 조절이 가능하며, Deque의 기능을 효율적으로 수행합니다.

ArrayDeque 주요 특징

  • 배열 기반: ArrayDeque는 내부적으로 배열을 사용하여, 양방향에서의 삽입과 삭제가 상수 시간에 이루어집니다. (O(1) 시간 복잡도)
    • 단, 배열의 크기가 꽉 찼을 경우에는 자동으로 배열 크기가 2배로 늘어나면서 복사가 이루어지기 때문에, 이 경우 성능 저하가 발생할 수 있습니다.
      • 다만, 대부분의 경우 성능에 큰 영향을 미치지 않으며, 일반적으로 대규모 데이터를 처리할 때도 매우 효율적입니다.
    • 참고로 원형 배열을 기반으로 만들어져 있는 자료구조이고, head와 tail의 인덱스 위치를 기반으로 삽입과 삭제가 이뤄진다는 특징이 있습니다.
  • 빠른 삽입과 삭제: 앞뒤 양쪽에서의 삽입과 삭제가 모두 빠르게 이루어지므로, 큐와 스택 모두의 동작을 효율적으로 수행할 수 있습니다.
    • ArrayDeque는 스택(Stack) 대신 java.util.Stack 클래스의 대안으로 권장됩니다. (Stack의 동기화 메서드들때문에 Stack은 성능이 그렇게 좋지 못합니다.)
    • LinkedList와 비교했을 때, 중간 삽입 및 삭제가 없는 경우에는 더 나은 성능을 보입니다. (다만 큐나 스택에서는 중간 삽입 과정이 있을 순 없어서 일반적으로 더 빠르다 생각하시면 됩니다.
  • Null 값 금지: ArrayDequenull 요소를 허용하지 않으며, null을 삽입하려고 하면 NullPointerException이 발생합니다.
  • 고정 크기 없음: 크기가 고정된 큐와 달리, ArrayDeque는 동적으로 크기가 조절되므로 더 유연하게 사용할 수 있습니다.

5-2.2. ArrayDeque 사용 예시

import java.util.ArrayDeque;
import java.util.Deque;

public class ArrayDequeExample {
    public static void main(String[] args) {
        // ArrayDeque로 Deque 생성
        Deque<String> deque = new ArrayDeque<>();

        // 1. 큐처럼 사용 (FIFO)
        deque.addLast("A");
        deque.addLast("B");
        deque.addLast("C");

        System.out.println("큐처럼 사용 (FIFO):");
        while (!deque.isEmpty()) {
            System.out.println(deque.removeFirst());
        }

        // 2. 스택처럼 사용 (LIFO)
        deque.addFirst("1");
        deque.addFirst("2");
        deque.addFirst("3");

        System.out.println("스택처럼 사용 (LIFO):");
        while (!deque.isEmpty()) {
            System.out.println(deque.removeFirst());
        }
    }
}
  • 위 예제에서 ArrayDeque큐(FIFO)스택(LIFO) 방식 모두를 유연하게 처리할 수 있습니다.
    • 큐로 사용 시 addLast(), removeFirst() 메서드를 통해 FIFO 방식으로 동작합니다.
      • 큐 방식에서는 offerLast()pollFirst()를 더 권장합니다.
    • 스택으로 사용할 때는 addFirst(), removeFirst() 메서드를 통해 LIFO 방식으로 동작합니다.
      • 마찬가지로 push(), pop()을 사용할 수 있습니다.

5-2.3. ArrayDeque 활용 팁

  • 스택과 큐의 대체 구현체: ArrayDeque는 스택과 큐 모두를 대체하는 강력한 구현체입니다.
    • java.util.Stack보다 성능이 우수하고, 동작 방식도 직관적이기 때문에 스택의 대체재로 권장됩니다.
  • 동적 크기 조절: ArrayDeque는 배열 기반으로 작동하지만, 자동으로 크기를 조절하기 때문에 데이터의 양에 맞게 유연하게 확장됩니다.
    • 대규모의 데이터를 다룰 때도 메모리 오버헤드가 적고 성능이 우수합니다.
  • 특정 작업에 유리
    • 양 끝에서 빠르게 삽입 및 삭제가 필요할 경우, 특히 큐와 스택 모두를 다루는 상황에서 유리합니다.
    • LinkedList와 달리, 중간에 삽입하거나 삭제하는 작업은 적합하지 않지만, 양 끝에서의 작업이 빈번할 경우 ArrayDeque가 더 효율적입니다.

5-2.4. ArrayDeque와 LinkedList 비교

  • ArrayDequeLinkedList는 모두 Deque의 구현체로, 큐와 스택의 기능을 제공합니다. 하지만 두 클래스는 성능과 메모리 관리 측면에서 차이가 있습니다.
비교 항목ArrayDequeLinkedList
내부 구조원형 배열 기반이중 연결 리스트 기반
삽입/삭제 성능앞뒤 삽입/삭제가 매우 빠름
(O(1), 단 배열 크기 증가 시 성능 저하)
앞뒤 삽입/삭제가 빠름 (O(1))
메모리 오버헤드배열 크기가 고정되어 있지 않고 유연하게 조정됨.
오버헤드 적음
각 노드가 추가 메모리를 사용
사용 시기양 끝 삽입/삭제가 많을 때,
빠른 성능이 필요할 때
중간 삽입/삭제가 빈번할 때
Null 값 허용 여부Null 값 허용 안 함Null 값 허용

마무리

  • 이번 포스팅에서는 QueueDeque 인터페이스, 그리고 그 대표적인 구현체인 PriorityQueue, LinkedList, ArrayDeque에 대해 다루었습니다.
    • QueueFIFO(First-In, First-Out) 구조로 순서에 맞춰 작업을 처리할 때 사용되며, PriorityQueueQueue를 구현하긴 했지만 우선순위에 따라 처리되는 특별한 큐 구조로, 내부적으로 Heap(힙) 자료구조를 사용합니다.
    • Deque양방향 큐로, 스택과 큐 두 가지 역할을 모두 할 수 있는 유연한 자료구조입니다. 대표적인 구현체로는 LinkedListArrayDeque가 있으며, 양 끝에서 삽입과 삭제가 빈번한 작업에 유리합니다.
  • 두 자료구조 모두 스레드 안전하지 않아서 멀티스레드 환경에서 사용할 경우 동기화가 필요할 수 있으며, 성능과 메모리 관리 측면에서 적합한 구현체를 선택하는 것이 중요합니다.
profile
일 때문에 포스팅은 잠시 쉬어요 ㅠ 바쁘다 바빠 모두들 화이팅! // Machine Learning (AI) Engineer & BackEnd Engineer (Entry)

0개의 댓글