🔷 순서가 있는 작업을 차례로 진행해야 할 때 순서를 결정해 주기 위해 사용하는 알고리즘
🔷 유향 비사이클 그래프(Directed Acyclic Graph, DAG)에 한해 가능하다.
🔷 진입 차수(in-degree)
: 특정 노드로 들어오는 간선의 개수
🔷 진출 차수(out-degree)
: 특정 노드에서 나가는 간선의 개수
💡 위상 정렬을 위해 꼭 필요한 차수는 진입 차수이다.
🔷 방법
1) 진입 차수가 0인 모든 노드를 큐에 삽입한다.
2) 큐가 공백상태가 될 때까지 반복 수행한다.
💡 Queue에서 꺼내지는 순서(Queue 들어온 순서)가 위상 정렬을 수행한 결과이다.'
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class DailyAPS {
public static String[] cook = {"", "재료구매", "양념장 만들기", "고기 재우기", "고기 손질", "제육 볶기", "식사", "뒷정리", "채소 손질", "밥 짓기" };
public static void main(String[] args) {
Scanner sc = new Scanner(input);
int V = sc.nextInt();
int E = sc.nextInt();
//인접행렬
int [][] adjArr = new int[V + 1][V + 1]; //1번 정점부터 시작해서 V번까지
int [] degree = new int [V + 1]; //진입차수를 저장할 배열
for(int i = 0; i < E; i++) {
int A = sc.nextInt();
int B = sc.nextInt();
//유향그래프
adjArr[A][B] = 1; //가중치가 따로 주어지지 않았기 때문에
//진입차수를 증가
degree[B]++;
}
//degree가 0인 정점만 들어갈 수 있는 큐 준비
Queue<Integer> queue = new LinkedList<>();
//초기에 진입차수가 0인 정점을 모두 큐에 넣기
for(int i = 1; i < V+1; i++) {
if(degree[i] == 0)
queue.add(i);
}
while(!queue.isEmpty()) {
int work = queue.poll(); //현재 작업하나 꺼낸다.
System.out.println(cook[work]); //작업 출력
for(int i = 0; i < V+1; i++) {
if(adjArr[work][i] == 1) {
adjArr[work][i] = 0; //간선 제거, 없어도 정답 출력에는 지장없음
degree[i]--; //진입차수 감소
if(degree[i] == 0) //진입차수가 0이라는 것은 선행 작업이 모두 완료되었다는 것,
queue.add(i); //큐에 들어가도 좋다.
}//연결확인
}//연결 끊는 작업
}
}
public static String input = "9 9\r\n" + "1 4\r\n" + "1 8\r\n" + "2 3\r\n" + "4 3\r\n" + "8 5\r\n" + "3 5\r\n"
+ "5 6\r\n" + "9 6\r\n" + "6 7\r\n" + "";
}
🔷 방법
1) 진입 차수가 0인 모든 노드에서 DFS 탐색을 수행한다.
2) DFS 수행 시
3) Stack이 공백 상태가 될 때까지 pop한다.
💡 Stack에서 꺼내지는 순서를 뒤집으면 위상 정렬을 수행한 결과이다.
import java.util.Scanner;
import java.util.Stack;
public class DailyAPS {
public static String[] cook = {"", "재료구매", "양념장 만들기", "고기 재우기", "고기 손질", "제육 볶기", "식사", "뒷정리", "채소 손질", "밥 짓기" };
static int V, E;
static int [][] adjArr;
static int[] degree;
static boolean[] visited;
static Stack<Integer> stack;
public static void main(String[] args) {
Scanner sc = new Scanner(input);
V = sc.nextInt();
E = sc.nextInt();
//인접행렬
adjArr = new int[V + 1][V + 1]; //1번 정점부터 시작해서 V번까지
degree = new int [V + 1]; //진입차수를 저장할 배열
visited = new boolean [V + 1];
stack = new Stack<>();
for(int i = 0; i < E; i++) {
int A = sc.nextInt();
int B = sc.nextInt();
//유향그래프
adjArr[A][B] = 1; //가중치가 따로 주어지지 않았기 때문에
//진입차수를 증가
degree[B]++;
}
//진입차수가 0인 원소들을 가지고 출발한다
for(int i = 1; i < V+1; i++) {
if(degree[i] == 0)
DFS(i);
}
while(!stack.isEmpty()) {
System.out.println(cook[stack.pop()]);
}
}
private static void DFS(int v) {
visited[v] = true; //현재 정점 방문 체크
for(int i = 1; i < V+1; i++) {
//인접하지만 방문하지 않은 정점들을 방문
if(adjArr[v][i] == 1 && !visited[i]) {
DFS(i); //깊이우선탐색
}
}
stack.add(v);
}
public static String input = "9 9\r\n" + "1 4\r\n" + "1 8\r\n" + "2 3\r\n" + "4 3\r\n" + "8 5\r\n" + "3 5\r\n"
+ "5 6\r\n" + "9 6\r\n" + "6 7\r\n" + "";
}
모든 정점을 방문하기 전에 Queue가 공백 상태가 되면 사이클이 존재하는 것이다.
(사이클이 존재하면 진입 차수가 0이 될 수 없음)
그래프의 유형은 DAG
여러 해답이 존재할 수 있다.
(진입 차수가 0인 값이 동시에 생성이 된다면 작성한 코드 방법에 따라 답이 달라진다.)
시간 복잡도: O(V + E)
이번주 APS는 헬이었지만, 그만큼 실력 향상은 확실한것 같다.