티스토리 뷰
문제
민오는 1번부터 N번까지 총 N개의 문제로 되어 있는 문제집을 풀려고 한다. 문제는 난이도 순서로 출제되어 있다. 즉 1번 문제가 가장 쉬운 문제이고 N번 문제가 가장 어려운 문제가 된다.
어떤 문제부터 풀까 고민하면서 문제를 훑어보던 민오는, 몇몇 문제들 사이에는 '먼저 푸는 것이 좋은 문제'가 있다는 것을 알게 되었다. 예를 들어 1번 문제를 풀고 나면 4번 문제가 쉽게 풀린다거나 하는 식이다. 민오는 다음의 세 가지 조건에 따라 문제를 풀 순서를 정하기로 하였다.
- N개의 문제는 모두 풀어야 한다.
- 먼저 푸는 것이 좋은 문제가 있는 문제는, 먼저 푸는 것이 좋은 문제를 반드시 먼저 풀어야 한다.
- 가능하면 쉬운 문제부터 풀어야 한다.
예를 들어서 네 개의 문제가 있다고 하자. 4번 문제는 2번 문제보다 먼저 푸는 것이 좋고, 3번 문제는 1번 문제보다 먼저 푸는 것이 좋다고 하자. 만일 4-3-2-1의 순서로 문제를 풀게 되면 조건 1과 조건 2를 만족한다. 하지만 조건 3을 만족하지 않는다. 4보다 3을 충분히 먼저 풀 수 있기 때문이다. 따라서 조건 3을 만족하는 문제를 풀 순서는 3-1-4-2가 된다.
문제의 개수와 먼저 푸는 것이 좋은 문제에 대한 정보가 주어졌을 때, 주어진 조건을 만족하면서 민오가 풀 문제의 순서를 결정해 주는 프로그램을 작성하시오.
입력
첫째 줄에 문제의 수 N(1 ≤ N ≤ 32,000)과 먼저 푸는 것이 좋은 문제에 대한 정보의 개수 M(1 ≤ M ≤ 100,000)이 주어진다. 둘째 줄부터 M개의 줄에 걸쳐 두 정수의 순서쌍 A,B가 빈칸을 사이에 두고 주어진다. 이는 A번 문제는 B번 문제보다 먼저 푸는 것이 좋다는 의미이다.
항상 문제를 모두 풀 수 있는 경우만 입력으로 주어진다.
출력
첫째 줄에 문제 번호를 나타내는 1 이상 N 이하의 정수들을 민오가 풀어야 하는 순서대로 빈칸을 사이에 두고 출력한다.
첫 번째 접근
가능하면 쉬운 문제를 풀되, 선행 문제를 풀어야 한다.
DFS와 우선순위 큐가 떠올랐다.
쉬운 문제(1부터 N)를 순회하면서 문제 번호가 가지고 있는 우선순위 큐를 조회하여 빈 큐를 가지고 있는 것부터 차례대로 출력시키는 로직을 생각해봤다.
import java.util.*;
import java.io.*;
public class Main {
static boolean[] visit;
static StringBuilder answer = new StringBuilder();
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
List<PriorityQueue<Integer>> pqList = new ArrayList<>();
int N = Integer.valueOf(st.nextToken());
int M = Integer.valueOf(st.nextToken());
// 초기화
visit = new boolean[N + 1];
for(int i = 0;i <= N;i++) {
pqList.add(new PriorityQueue<>());
}
for (int i = 0; i < M; i++) {
st = new StringTokenizer(br.readLine());
int A = Integer.valueOf(st.nextToken());
int B = Integer.valueOf(st.nextToken());
pqList.get(B).add(A);
}
for(int i = 1;i<=N;i++) {
if(visit[i]) continue;
dfs(pqList, i);
visit[i] = true;
answer.append(i);
answer.append(" ");
}
System.out.println(answer.toString());
}
private static void dfs(List<PriorityQueue<Integer>> pqList , int num) {
if(pqList.get(num).isEmpty()) {
visit[num] = true;
answer.append(num);
answer.append(" ");
}
PriorityQueue<Integer> pq = pqList.get(num);
while(!pq.isEmpty()) {
int nextNum = pq.poll();
if(visit[nextNum])continue;
dfs(pqList, nextNum);
}
}
}
백준에서 주어진 테스트케이스는 통과했지만 결과는 틀렸다.
위상 정렬
위 문제는 위상 정렬로 풀어야 한다.
위상 정렬이라고 유추할 수 있는 키워드는 선행 문제, 항상 문제를 풀 수 있는 경우만 주어진다 이다.
선행 문제
위상 정렬 자체가 순서가 존재하는 문제를 해결할 때 사용하는 알고리즘이다.
항상 문제를 풀 수 있는 경우만 주어진다
문제를 풀 수 있는 경우, 즉 사이클이 있는 경우는 없다라고 명시해준다.
위상 정렬은 사이클이 있는 경우 활용할 수 없으므로 이 명세가 위상 정렬 문제임을 알려준다.
import java.util.*;
import java.io.*;
public class Main {
static int N;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
List<List<Integer>> array = new ArrayList<>();
N = Integer.valueOf(st.nextToken());
int M = Integer.valueOf(st.nextToken());
for (int i = 0; i <= N; i++) {
array.add(new ArrayList<>());
}
int[] indegree = new int[N+1];
for (int i = 0; i < M; i++) {
st = new StringTokenizer(br.readLine());
int A = Integer.valueOf(st.nextToken());
int B = Integer.valueOf(st.nextToken());
array.get(A).add(B);
indegree[B]++;
}
topologicalSort(indegree, array);
}
private static void topologicalSort(int[] indegree, List<List<Integer>> array) {
PriorityQueue<Integer> q = new PriorityQueue<>();
Queue<Integer> result = new LinkedList<>();
// 2. indegree가 0인 정점을 큐에 삽입한다.
for (int i = 1; i <= N; i++) {
if (indegree[i] == 0) {
q.add(i);
}
}
/**
* 3. 큐에서 원소를 꺼내 해당 원소와 간선을 제거한다.
* 4. 만약 indegree가 0 이 된다면 큐에 넣기
* 5. 큐가 빌 때까지 3, 4 과정을 반복한다.
*/
while (!q.isEmpty()) {
int node = q.poll();
result.add(node);
// 큐에서 원소를 꺼내 해당 원소와 간선을 제거한다.
for (Integer i : array.get(node)) {
indegree[i]--;
// 만약 indegree가 0 이 된다면 큐에 넣기
if (indegree[i] == 0) {
q.add(i);
}
}
}
StringBuilder answer = new StringBuilder();
answer.append(result.poll());
while(!result.isEmpty()) {
answer.append(" ");
answer.append(result.poll());
}
System.out.println(answer.toString());
}
}
찾아보니 위상 정렬을 DFS와 우선순위 큐(힙)으로 구현할 수 있다고 한다.
하지만 위에서 푼 것처럼 indegree와 queue를 활용하여 푸는 것이 더 깔끔하므로 다음에도 이와 같이 풀자.
'알고리즘' 카테고리의 다른 글
BOJ1005 ACM Craft java - 위상정렬[3] (0) | 2022.01.22 |
---|---|
BOJ2252 줄 세우기 java - 위상정렬 [2] (0) | 2022.01.22 |
BOJ1715 카드 정렬하기 java (0) | 2022.01.21 |
BOJ1927 최소 힙 java (0) | 2022.01.21 |
BOJ1991 트리 순회 java (0) | 2022.01.19 |
- Total
- Today
- Yesterday
- 객체지향
- Algorithm
- Olympiad
- kotest
- MSA
- 테라폼
- 이펙티브 자바
- 프로그래머스
- AWS
- 이팩티브 자바
- 코테
- 백준
- 클린 코드
- BOJ
- 클린 아키텍처
- 디자인패턴
- Java
- 알고리즘
- kkoon9
- C++
- 정규표현식
- Spring
- JPA
- Kotlin
- node.js
- Effective Java
- programmers
- Spring Boot
- 디자인 패턴
- BAEKJOON
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |