데블 아니고 데블리

운동,햄버거, 개발 좋아요

🐷💻📝

프로그래밍/자바

[자료구조] Stack, Queue, Deque

데블아니고데블리 2024. 4. 4. 01:01

오늘은 자료구조에 대해 강의를 듣고 구현해 보는 시간을 가졌는데, 깊이있게 공부해 본 경험은 처음이라 어려웠다.

 

[01. Stack]

- stack 자료구조는 후입선출 : 나중에 들어간게 처음으로 나온다(Last in First Out, LIFO) 구조입니다.

저는 세로구조라고 생각해요.. 맨 나중에 들어간 것 부터 꺼낸다 = 맨 위에서부터 꺼낸다..

일단 선언하고 데이터를 집어 넣는 것 부터 해야겠지요..

 

- 기본 연산

push: 스택의 맨 위에 요소를 추가.

pop: 스택의 맨 위 요소를 제거하고 그 값을 반환.

peek: 스택의 맨 위 요소를 조회.

 

import java.util.*;

public class StackExample {
    public static void main(String[] args) {
        // Stack 선언
        Stack<Integer> stack = new Stack<>();

        // 요소 추가
        stack.push(10); // 10 추가
        stack.push(20); // 20 추가
        stack.push(30); // 30 추가

        int peek = stack.peek();;

        // 스택의 상태 출력
        System.out.println("스텍은 ? " + stack);
        //출력 : 스택은? [10, 20, 30]

        System.out.println("스텍 맨위?  " + peek);
        //출력 : 스텍 맨위? 30 

        // 요소 추가 후 스택의 크기 출력
        System.out.println(stack.size());
        //출력 : 3
    }
}

 

저는 위에 그림처럼 쌓는다라고 생각했네요. 그래서 맨 위에 쌓인 데이터를 조회할 때는 peek()를 사용해 조회해줍니다.

 

꺼낼때는 pop()메서드를 사용합니다.

pop 은 위에서 설명한 것 처럼 꺼내고 그 요소를 지워버립니다... 코드와 그림을 보죠..

package src.com.company.week2.day1;

import java.util.Stack;

public class StackExample {
    public static void main(String[] args) {
        // Stack 선언
        Stack<Integer> stack = new Stack<>();

        // 요소 추가
        stack.push(10); // 10 추가
        stack.push(20); // 20 추가
        stack.push(30); // 30 추가

        int poppedElement = stack.pop(); // 스택에서 요소 꺼내고 반환

        // 꺼낸 요소 출력
        System.out.println("꺼낸 요소는?: " + poppedElement);
        // 출력 : 꺼낸 요소는 ? 30

        // 꺼낸 요소를 제외한 스택의 상태 출력
        System.out.println("꺼내고 난 뒤의 상태: " + stack);
        // 출력 : 꺼내고 난 뒤의 상태: [10, 20]
    }

}

 

 

 

- 사용하는 이유

1. 역추적(back tracking) : 데이터 임시저장소와 같은 역할 peek() 해서 조회해 올 수도, 원하는 값과 다르면 pop 해서 

재귀적인 알고리즘, 깊이우선 탐색에서 재귀 호출할 때 함수 호출 상태를 저장할 수 있음 => 이로 인해 알고리즘의 이전 상태로 되돌아 갈 수 있음.

 

2. 괄호 문제 : 오늘 과제로 나온 것

괄호의 쌍이 올바르게 열고 닫혔는지, 여는 괄호가 오면 스택에 push 하고 닫는 괄호가 나오면 pop 한다.

 


[02. Queue]

- Queue 자료구조는 선입선출(First In First Out) 구조입니다. 

저는 헷갈려서 스택만 외우는데, 큐는 짤주머니를 생각하고는 합니다.. 먼저 들어간 생크림이 먼저 나오는...? 

 

- 기본연산 

offer  : 큐의 끝에 요소를 추가.

poll : 큐의 첫번째 요소를 제거 후 값 반환.

peek : stack 과 같이 맨 위의 요소를 조회

 

import java.util.Queue;

public class QueueExample {
    public static void main(String[] args) {
        // Queue 선언
        Queue<String> queue = new LinkedList<>();

        // 요소 추가
        queue.offer("짜장면"); // 요소 추가
        queue.offer("치킨"); // 요소 추가
        queue.offer("떡볶이"); // 요소 추가
        
        String best = queue.peek();

        // 작업 대기열 출력
        System.out.println("내가 지금 먹고싶은건 " + queue);
        // 출력 : 내가 지금 먹고싶은건 [짜장면, 치킨, 떡볶이]
        
        System.out.println("가장 먹고싶은건 ? " + best);
        // 출력 : 가장 먹고싶은건 ? 짜장면

    }
}

 

 

이제 꺼내는 방법도 알아야.. 겠죠?

 

1. poll() 메서드 사용하여 하나씩 꺼내 봅시다.

package src.com.company.week2.day1;

import java.util.LinkedList;
import java.util.Queue;

public class QueueExample {
    public static void main(String[] args) {
        // Queue 선언
        Queue<String> queue = new LinkedList<>();

        // 요소 추가
        queue.offer("짜장면"); // 작업 추가
        queue.offer("치킨"); // 작업 추가
        queue.offer("떡볶이"); // 작업 추가

        // 작업 대기열 출력
        System.out.println("내가 지금 먹고싶은건 " + queue);
        // 출력 : 내가 지금 먹고싶은건 [짜장면, 치킨, 떡볶이]

        // peek은 조화만 한다
        String best = queue.peek();

        String first = queue.poll();
        String second = queue.poll();
        String third = queue.poll();
        

        System.out.println("가장 먹고싶은건 ? " + best);
        // 출력 : 가장 먹고싶은건 ? 짜장면

        System.out.println("처음으로 나오는건 ? " + first);
        // 출력 : 처음으로 나오는건 ? 짜장면

        System.out.println("두번째로 나오는건 ? " + second);
        // 출력 : 처음으로 나오는건 ? 치킨

        System.out.println("세번째로 나오는건 ? " + third);
        // 출력 : 처음으로 나오는건 ? 떡볶이

        /** 요소 다 빼고 나서는 ? */
        System.out.println("내가 지금 먹고싶은건 " + queue);
        // 출력 : 내가 지금 먹고싶은건 []


    }
}

 

 

2. for 문을 돌려서 요소를 다 꺼내봅시다.

package src.com.company.week2.day1;

import java.util.LinkedList;
import java.util.Queue;

public class QueueExample {
    public static void main(String[] args) {
        // Queue 선언
        Queue<String> queue = new LinkedList<>();

        // 요소 추가
        queue.offer("짜장면"); // 작업 추가
        queue.offer("치킨"); // 작업 추가
        queue.offer("떡볶이"); // 작업 추가

        // 작업 대기열 출력
        System.out.println("내가 지금 먹고싶은건 " + queue);
        // 출력 : 내가 지금 먹고싶은건 [짜장면, 치킨, 떡볶이]
        
        
        // 작업 처리 (처음에 추가된 작업부터 처리됨)
        while (!queue.isEmpty()) {
            String task = queue.poll(); // 작업 꺼내기
            System.out.println("지금 먹고있는 것은?: " + task);
        }
        /**
         * 출력
         * 지금 먹고있는 것은?: 짜장면
         * 지금 먹고있는 것은?: 치킨
         * 지금 먹고있는 것은?: 떡볶이
         * */


        /** 요소 다 빼고 나서는 ? */
        System.out.println("내가 지금 먹고싶은건 " + queue);
        // 출력 : 내가 지금 먹고싶은건 []
        
    }
}

 

예시를 음식으로 이야기 한건.. 시간이 시간이라.. 배고파서... 입니다...

 

- 사용하는 이유

1. 작업 대기열 : 여러 작업을 "순서대로" 구현해야 할 때

2. 캐시(Cache) : 새로운 뎅터가 추가되면 Queue의 맨 뒤에 추가되고, 오래된 데이터는 제거

 

+) 우선순위큐 ? 


 

[03. Deque]

- Deque(Double Ended Queue) 은 양방향에서 데이터를 추가하거나 제거할 수 있다.

- Queue 와 Stack의 기능을 모두 가지고 있다.

⭐️ Deque의 구현체로는 1) ArrayDeque, 2) LinkedList 가 있다.

 

  ArrayDeque LinkedList
접근 시간 - 배열 -> 인덱스 빠른 접근(O(1)) - 배열 순회해서 찾음(O(n))
메모리 사용 - 연속적, 캐시효율성, 메모리 오버헤드 없음 - 앞, 뒤 노드(참조) 가 필요해 메모리 사용량 ArrayDeque 보다 많음
언제 쓸까 ? 요소에 대한 빠른 접근 필요할 때(조회) 삽입, 삭제가 많이 일어날 때(값 변동)
순서가 있는 접근이 필요할 때
내부 구현 - 동적으로 크기가 조절되는 배열 사용
- 요소 추가, 제거 가능
- 인덱스 사용해 요소에 접근 가능
- 이중 연결 리스트로 구현되어 있음
- 요소 추가, 제거 시 노드도 조회
- 인덱스 사용 가능하나, 데이터(요소) 조회 시, 이전노드, 다음 노드 참조 포함

 

- 기본 연산

  값 추가 값 제거
예외 있음 - addFirst, addLast, add
- IllegalStateException, 용량 초과 예외
- removeFirst, removeLast, remove
- NoSuchElementException
예외 없음 - offerFirst, offerLast, offer
- 실패 시 false 반환
- pollFirst, pollLast, poll
- 실패 시 null 반환

 

코드와 출력문으로 봅시다..

package src.com.company.week2.day1;

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

public class DequeExample {
    public static void main(String[] args) {
        // Deque 선언 (ArrayDeque 사용)
        Deque<Integer> deque1 = new ArrayDeque<>();

        // Deque에 요소 추가 (양쪽 끝에 추가)
        deque1.offerFirst(1); // 맨 앞에 추가
        deque1.offer(2);
        deque1.offer(3);
        deque1.offerLast(4);  // 맨 뒤에 추가


        // Deque 출력
        System.out.println("데큐 1번: " + deque1);
        // 출력 : 데큐 1번: [1, 2, 3, 4]

        // Deque1에서 요소 제거 (양쪽 끝에서 제거)
        int pollFirst = deque1.pollFirst(); // 맨 앞에서 제거
        int pollLast = deque1.pollLast();   // 맨 뒤에서 제거

        // 제거된 요소 출력
        System.out.println("첫번째로 제거된것 ? " + pollFirst);
        // 출력 : 첫번째로 제거된것 ? 1
        System.out.println("마지막 제거 요소 ? " + pollLast);
        // 출력 : 마지막 제거 요소 ? 4

        // 남아있는 deque ?
        System.out.println("맨 앞 맨 뒤 제거 후?  " + deque1);
        // 출력 : 맨 앞 맨 뒤 제거 후?  [2, 3]


        System.out.println("============================================");

        Deque<String> deque2 = new LinkedList<>();

        deque2.addFirst("I am first");
        deque2.add("first 다음으로 넣음");
        deque2.addLast("I am Last");


        // Deque 출력
        System.out.println("데큐 2번: " + deque2);
        // 출력 : 데큐 2번: [I am first, first 다음으로 넣음, I am Last]

        // Deque2에서 요소 제거 (양쪽 끝에서 제거)
        String removeFirst = deque2.removeFirst(); // 맨 앞에서 제거
        String removeLast = deque2.removeLast();   // 맨 뒤에서 제거

        // 제거된 요소 출력
        System.out.println("첫번째로 제거된것 ? " + removeFirst);
        // 출력 :첫번째로 제거된것 ? I am first
        System.out.println("마지막 제거 요소 ? " + removeLast);
        // 출력 : 마지막 제거 요소 ? I am Last

        // 남아있는 deque ?
        System.out.println("맨 앞 맨 뒤 제거 후?  " + deque2);
        // 출력 : 맨 앞 맨 뒤 제거 후?  [first 다음으로 넣음]
        
    }
}

 

 


끝!