#1655 가운데를 말해요
난이도 : 골드 2
유형 : 자료구조 / 힙 / 우선순위 큐
▸ 문제
수빈이는 동생에게 "가운데를 말해요" 게임을 가르쳐주고 있다. 수빈이가 정수를 하나씩 외칠때마다 동생은 지금까지 수빈이가 말한 수 중에서 중간값을 말해야 한다. 만약, 그동안 수빈이가 외친 수의 개수가 짝수개라면 중간에 있는 두 수 중에서 작은 수를 말해야 한다.
예를 들어 수빈이가 동생에게 1, 5, 2, 10, -99, 7, 5를 순서대로 외쳤다고 하면, 동생은 1, 1, 2, 2, 2, 2, 5를 차례대로 말해야 한다. 수빈이가 외치는 수가 주어졌을 때, 동생이 말해야 하는 수를 구하는 프로그램을 작성하시오.
▸ 입력
첫째 줄에는 수빈이가 외치는 정수의 개수 N이 주어진다. N은 1보다 크거나 같고, 100,000보다 작거나 같은 자연수이다. 그 다음 N줄에 걸쳐서 수빈이가 외치는 정수가 차례대로 주어진다. 정수는 -10,000보다 크거나 같고, 10,000보다 작거나 같다.
▸ 출력
한 줄에 하나씩 N줄에 걸쳐 수빈이의 동생이 말해야하는 수를 순서대로 출력한다.
문제 풀이
큐는 FIFO 구조이다. 하지만 우선순위 큐는 들어간 순서에 상관없이 우선순위가 높은 데이터가 먼저 나온다. 그래서 처음에는 값을 하나씩 넣어줄 때 마다 자동으로 정렬을 해주는 우선순위 큐에 저장을 하고 중간 값을 iterator를 통해 탐색을 하려 했지만 잘 되지 않았다.
☛ 이유는 글 맨 아래 참고
우선순위 큐는 힙(Heap)이라는 자료구조를 가지고 구현할 수 있다.
힙(Heap)
힙(heap)은 최댓값 및 최솟값을 찾아내는 연산을 빠르게 하기 위해 고안된 완전이진트리(complete binary tree)를 기본으로 한 자료구조(tree-based structure)이다.
힙에는 두가지 종류가 있으며, 부모노드의 키값이 자식노드의 키값보다 항상 큰 힙을 '최대 힙', 부모노드의 키값이 자식노드의 키값보다 항상 작은 힙을 '최소 힙'이라고 부른다.
정리하면 ,
최소 힙(Min Heap)은 완전 이진트리이면서, 루트 노드로 올라갈 수록 값이 작아지는 구조이고,
최대 힙(Max Heap)은 완전 이진트리이면서, 루트 노드로 올라갈 수록 값이 커지는 구조이다.
우선순위 큐를 가지고 이 두 가지 종류의 힙의 개념(최대 힙과 최소 힙)을 구현함으로써 이 문제를 해결할 수 있다. 가운데 있는 값은 최소 힙에 있는 루트 노드에 있는 수를 꺼내주면 된다.
설계
- 최소 힙과 최대 힙의 크기가 같으면 최대 힙(MaxHeap)에 값을 저장해준다.
- 아니면 최소 힙(MinHeap)에 값을 저장한다.
- 최대 힙의 루트노드가 최소 힙의 루트노드보다 크면 위치를 바꿔준다. maxHeap.peek() > minHeap.peek()
- 가운데의 수는 최대 힙의 루트노드를 출력해주면 된다.
시뮬레이션
1, 5, 2, 10, -99, 7, 5를 순서대로 넣어보자
1) 최소 힙과 최대 힙의 사이즈가 같을 때는 최대 힙에 값을 추가해준다. MaxHeap.add(num);
2) 다음 값은 MinHeap에 넣어준다. 만약 최대 힙의 루트노드가 더 클 경우 (ex. 입력값 순서가 5→ 1 로 이루어졌다면) 각 루트노드의 위치를 바꿔줘야 한다.
if(!maxHeap.isEmpty() && !minHeap.isEmpty()) {
if(maxHeap.peek() > minHeap.peek()) {
int tmp = maxHeap.poll();
maxHeap.offer(minHeap.poll());
minHeap.offer(tmp);
}
}
3) 최소 힙은 루트노드가 가장 큰 값을 가지는 구조이다.(내림차순으로 정렬) 그래서 값을 추가해주면 자동으로 루트노드로 갈수록 값이 커지도록 우선순위가 바뀐다.
4) 최대 힙은 최소 힙과 반대로 루트노드가 가장 작은 값을 가지는 구조이다.(오름차순 정렬) 그래서 이 힙 또한 자동으로 루트노드로 갈수록 값이 작아지도록 우선순위가 바뀐다.
5) 최대 힙과 최소 힙크기가 같으면 최소 힙에 값 추가하는 식으로 계속 넣어주면 된다.
풀이 코드
우선순위 큐로 최대 힙, 최소 힙을 구현할 수 있다는 점을 꼭 기억해두자!
import java.io.*;
import java.util.PriorityQueue;
import java.util.Queue;
public class Main {
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringBuilder sb = new StringBuilder();
int n = Integer.parseInt(br.readLine());
Queue<Integer> maxHeap = new PriorityQueue<>((o1,o2) -> o2-o1); // 내림차순
Queue<Integer> minHeap = new PriorityQueue<>((o1,o2) -> o1-o2); // 오름차순
for(int i=0; i<n; i++) {
int num = Integer.parseInt(br.readLine());
if(maxHeap.size() == minHeap.size()) maxHeap.add(num);
else minHeap.add(num);
// maxHeap이 더 클 경우 원소 switch
if(!maxHeap.isEmpty() && !minHeap.isEmpty()) {
if(maxHeap.peek() > minHeap.peek()) {
int tmp = maxHeap.poll();
maxHeap.offer(minHeap.poll());
minHeap.offer(tmp);
}
}
sb.append(maxHeap.peek()+"\n");
}
System.out.println(sb.toString());
}
}
+ PriorityQueue 사용시 주의할점
Proirty Queue로 최대 힙, 최소 힙을 구현하고 iterator로 queue를 탐색했을 때 우선순위 순서가 보장되지 않는다.
// 우선순위 보장 x
Iterator<Integer> it = minHeap.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
while(!minHeap.isEmpty()) {
System.out.println(minHeap.poll());
}
결과를 보면 랜덤으로 호출되는 것을 볼 수 있다.
'Dot Algo∙ DS > PS' 카테고리의 다른 글
[BOJ] 백준 1918번 후위 표기식 (Java) (0) | 2021.06.20 |
---|---|
[BOJ] 백준 1715번 카드 정렬하기 (Java) (0) | 2021.06.19 |
[BOJ] 백준 14502번 연구소 (Java) (0) | 2021.06.18 |
[BOJ] 백준 6549번 히스토그램에서 가장 큰 직사각형 (Java) (0) | 2021.06.17 |
[BOJ] 백준 17069번 파이프 옮기기 2 (Java) (0) | 2021.06.17 |