πŸ“‚ 자료ꡬ쑰/μ •λ ¬

μœ„μƒ μ •λ ¬(Topology Sort)

Amenable 2023. 3. 7. 23:49

1. μ •μ˜ 🧊

μœ„μƒ μ •λ ¬

  μœ„μƒ μ •λ ¬(Topology Sort)μ΄λž€ μˆœμ„œκ°€ μ •ν•΄μ Έ μžˆλŠ” μž‘업을 μ°¨λ‘€λŒ€λ‘œ μˆ˜ν–‰ν•  λ•Œ κ·Έ μˆœμ„œλ₯Ό κ²°μ •ν•΄ μ£ΌλŠ” μ•Œκ³ λ¦¬μ¦˜μ΄λ‹€. 

  κ΅μœ‘κ³Όμ •μ˜ μ„ μˆ˜κ³Όλͺ©μ΄ μœ„μƒ 정렬을 μ„€λͺ…ν•˜λŠ” κ°€μž₯ μ μ ˆν•œ μ˜ˆμ‹œμ΄λ‹€. 컴퓨터 곡학을 μ „κ³΅ν•˜κ³  μžˆλŠ” 학생은 λ‹€μŒ 전곡과λͺ©(ex. μ•Œκ³ λ¦¬μ¦˜)을 λ“£κΈ° μœ„ν•΄μ„œλŠ” μ„ μˆ˜ κ³Όλͺ©(ex. 자료ꡬ쑰)을 ν•„μˆ˜μ μœΌλ‘œ λ“€μ–΄μ•Ό ν•œλ‹€. 

κΈ°μ΄ˆμ»΄ν“¨ν„°ν”„λ‘œκ·Έλž˜λ° → 자료ꡬ쑰 → μ•Œκ³ λ¦¬μ¦˜
자료ꡬ쑰 → λ…Όλ¦¬νšŒλ‘œλ°μ„€κ³„ → λ…Όλ¦¬νšŒλ‘œμ‹€ν—˜
μ•Œκ³ λ¦¬μ¦˜ → λ…Όλ¦¬νšŒλ‘œλ°μ„€κ³„ → λ…Όλ¦¬νšŒλ‘œμ‹€ν—˜

  λ˜ν•œ, μœ„μƒμ •λ ¬μ€ μœ„μ™€ 같이 μ—¬λŸ¬ 개의 κ²½λ‘œκ°€ μ‘΄μž¬ν•  수 μžˆλ‹€.

  λ§ˆμ§€λ§‰ νŠΉμ§•μœΌλ‘œλŠ” μœ„μƒ 정렬은 λΉ„μˆœν™˜ 유ν–₯ κ·Έλž˜ν”„(DAG - Directed Acyclic Graph)μ—λ§Œ 적용이 κ°€λŠ₯ν•˜λ‹€. 즉, 사이클이 λ°œμƒν•˜μ§€ μ•ŠλŠ” λ°©ν–₯ κ·Έλž˜ν”„μ—¬μ•Ό ν•œλ‹€.

 

2. λ™μž‘ 방식(BFS) πŸ₯€

  μœ„μƒ 정렬은 BFS와 DFSλ₯Ό 톡해 κ΅¬ν˜„ν•  수 μžˆλ‹€. μš°μ„  BFSλ₯Ό μ΄μš©ν•˜λŠ” 방법을 λ¨Όμ € μ•Œμ•„λ³΄μž.

  BFSλ₯Ό μ΄μš©ν•˜λŠ” 경우 μ§„μž… μ°¨μˆ˜μ™€ Queueλ₯Ό ν™œμš©ν•œλ‹€. λ™μž‘ 방식은 μ•„λž˜μ™€ κ°™λ‹€.

[λ™μž‘ 방식]

  1. μ§„μž… μ°¨μˆ˜κ°€ 0인 μ •점을 Queue에 λ„£λŠ”λ‹€.
  2. Queueμ—μ„œ μ›μ†Œμ— μ—°κ²°λœ 간선을 μ œκ±°ν•œλ‹€.
  3. 간선이 μ œκ±°κ°€ λœ ν›„ μ§„μž… μ°¨μˆ˜κ°€ 0이 λœ μ •점을 Queue에 λ„£λŠ”λ‹€.
  4. Queueκ°€ λΉŒ λ•ŒκΉŒμ§€ 2번과 3λ²ˆμ„ λ°˜λ³΅ν•œλ‹€.

  μž‘업이 λλ‚œ ν›„ λͺ¨λ“  정점을 λ°©λ¬Έν–ˆλ‹€λ©΄ μœ„μƒ 정렬이 κ°€λŠ₯ν•œ κ·Έλž˜ν”„μ΄κ³  κ·Έλ ‡μ§€ λͺ»ν•œ 상황은 사이클이 μ‘΄μž¬ν•˜λŠ” κ²½μš°μ΄λ‹€. μ•„λž˜μ˜ μœ„μƒ 정렬이 κ°€λŠ₯ν•œ μ˜ˆμ‹œμ™€ μœ„μƒ 정렬이 λΆˆκ°€λŠ₯ν•œ μ˜ˆμ‹œλ₯Ό 톡해 ν•œλ²ˆ 더 이해λ₯Ό μ‚΄νŽ΄λ³΄μž.

[μœ„μƒ 정렬이 κ°€λŠ₯ν•œ μ˜ˆμ‹œ]

정점 1 2 3 4 5 6
μ§„μž… 차수 0 0 1 2 1 2

 

정점 1 2 3 4 5 6
μ§„μž… 차수 λ°©λ¬Έ λ°©λ¬Έ 0 0 1 2

정점 1 2 3 4 5 6
μ§„μž… 차수 λ°©λ¬Έ λ°©λ¬Έ λ°©λ¬Έ λ°©λ¬Έ 0 1

정점 1 2 3 4 5 6
μ§„μž… 차수 λ°©λ¬Έ λ°©λ¬Έ λ°©λ¬Έ λ°©λ¬Έ λ°©λ¬Έ 0

정점 1 2 3 4 5 6
μ§„μž… 차수 λ°©λ¬Έ λ°©λ¬Έ λ°©λ¬Έ λ°©λ¬Έ λ°©λ¬Έ λ°©λ¬Έ

[μœ„μƒ 정렬이 λΆˆκ°€λŠ₯ν•œ μ˜ˆμ‹œ]

정점 1 2 3 4 5 6
μ§„μž… 차수 0 0 2 3 1 1

정점 1 2 3 4 5 6
μ§„μž… 차수 λ°©λ¬Έ λ°©λ¬Έ 1 1 1 1

  사이클이 있으면 아직 방문을 ν•˜μ§€ λͺ»ν–ˆμ§€λ§Œ μ •μ λ“€μ˜ μ§„μž… μ°¨μˆ˜κ°€ 0이 μ•„λ‹ˆλΌμ„œ 더 이상 방문을 ν•˜μ§€ λͺ»ν•˜λŠ” 상황이 λ°œμƒν•œλ‹€. 즉, λͺ¨λ“  정점을 λ°©λ¬Έν•˜μ§€ λͺ»ν•œ κ²½μš°λŠ” 사이클이 μžˆμ–΄μ„œ μœ„μƒ 정렬이 λΆˆκ°€λŠ₯ν•œ 상황이라고 ν•  수 μžˆλ‹€.

 

3. BFSλ₯Ό μ΄μš©ν•œ κ΅¬ν˜„ μ½”λ“œ (JAVA) 🍹

첫 μ€„μ—λŠ” μ •μ μ˜ κ°œμˆ˜μ™€ κ°„μ„ μ˜ 개수λ₯Ό λ°›μ•„μ˜¨λ‹€. 그리고 κ°„μ„ μ˜ 개수만큼 좜발 정점과 도착 정점듀을 보여쀀닀. μ•„λž˜λŠ” μœ„μ—μ„œ μ‚΄νŽ΄λ³Έ μ˜ˆμ‹œλ₯Ό μž…λ ₯으둜 λ‚˜νƒ€λ‚Έ 것이닀.

6 6
1 3
1 4
2 4
3 5
4 6
5 6
public class TopologySorting {
	static int N, M;
	static Node[] adjNode;
	static int[] inDegree;

	static class Node {
		int vertex;
		Node link;

		public Node(int vertex, Node link) {
			super();
			this.vertex = vertex;
			this.link = link;
		}
	}

	public static void main(String[] args) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(in.readLine(), " ");
		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());

		// 인접 리슀트
		adjNode = new Node[N + 1];
		// μ§„μž… 차수
		inDegree = new int[N + 1];
		int from, to;

		for(int i = 0; i < M; i++) {
			st = new StringTokenizer(in.readLine(), " ");
			from = Integer.parseInt(st.nextToken());
			to = Integer.parseInt(st.nextToken());
			// 인접 리슀트 μΆ”κ°€
			adjNode[from] = new Node(to, adjNode[from]);
			// μ§„μž… 차수 μΆ”κ°€
			inDegree[to]++;
		}

		// μœ„μƒ 정렬을 톡해 μ–»μ–΄μ§„ 경둜λ₯Ό
		// resultFromTopologySort에 μ €μž₯
		ArrayList<Integer> resultFromTopologySort = topologySortByBFS();
		
		if(resultFromTopologySort.size() == N) {
			System.out.println("μœ„μƒ μ •λ ¬ κ°€λŠ₯");
			for(Integer i : resultFromTopologySort) {
				System.out.print(i + " ");
			}
		}
		else {
			System.out.println("μœ„μƒ μ •λ ¬ λΆˆκ°€λŠ₯ (사이클 λ°œμƒ)");
		}
	}

	static ArrayList<Integer> topologySortByBFS(){
		Queue<Integer> queue = new ArrayDeque<>();
		// μ§„μž…μ°¨μˆ˜κ°€ 0인 정점 μΆ”κ°€
		for(int i = 1; i <= N; i++) {
			if(inDegree[i] == 0)
				queue.offer(i);
		}

		ArrayList<Integer> visitedNode = new ArrayList<>();
		while(!queue.isEmpty()) {
			int currentNode = queue.poll();
			visitedNode.add(currentNode);

			for(Node temp = adjNode[currentNode]; temp != null; temp = temp.link) {
				// μ—°κ²°λœ 간선을 μ œκ±°ν•΄μ£ΌλŠ” ν–‰μœ„
				inDegree[temp.vertex]--;
				
				// μ§„μž… μ°¨μˆ˜κ°€ 0이 된 경우
				if(inDegree[temp.vertex] == 0) {
					queue.offer(temp.vertex);
				}
			}
		}

		return visitedNode;
	}
}

 

4. λ™μž‘ 방식(DFS) 🍸

  DFSλ₯Ό μ΄μš©ν•˜λŠ” 경우 μ§„μž… μ°¨μˆ˜μ™€ λ°©λ¬Έ μ—¬λΆ€λ₯Ό ν™œμš©ν•œλ‹€. DFS의 경우 Stack을 μ΄μš©ν•˜λŠ” 방법도 μžˆλ‹€. Stack을 μ΄μš©ν•˜λ©΄ 경둜λ₯Ό μ‰½κ²Œ μ•Œ 수 μžˆμ§€λ§Œ 사이클 λ°œμƒ μ—¬λΆ€λ₯Ό μ•ŒκΈ° μ–΄λ ΅λ‹€. κ·Έλž˜μ„œ 이번 κΈ€μ—μ„œλŠ” μœ„μƒ 정렬인지 μ•„λ‹Œμ§€λ₯Ό μ‰½κ²Œ μ•Œμ•„λ³΄κΈ° μœ„ν•˜μ—¬ μ§„μž…μ°¨μˆ˜μ™€ λ°©λ¬Έμ—¬λΆ€λ₯Ό μ‚¬μš©ν•˜λŠ” 방식을 μ΄μš©ν•΄ 보자. (λ¬Όλ‘  이 방법도 경둜λ₯Ό 확인할 수 μžˆλ‹€.)

[λ™μž‘ λ°©μ‹]

  1. μ§„μž… μ°¨μˆ˜κ°€ 0인 μ •점을 λ¨Όμ € λ°©λ¬Έν•œλ‹€.
  2. DFS λ°©μ‹μ„ μ΄μš©ν•˜μ—¬ κ³„μ†ν•΄μ„œ λ‹€μŒ μ •μ μœΌλ‘œ μ§„ν–‰ν•œλ‹€.
    μ΄λ•Œ, λ°©λ¬Έ μ—¬λΆ€λ₯Ό ν‘œμ‹œν•΄ μ€€λ‹€.
  3. 더 μ΄μƒ μ§„ν–‰ν•  μˆ˜ μ—†μœΌλ©΄ λ°©λ¬Έ μ—¬λΆ€λ₯Ό ν•΄μ œν•˜λ©΄μ„œ λŒμ•„μ˜¨λ‹€.
  4. 아직 λ°©λ¬Έν•˜μ§€ μ•Šμ€ μ§„μž…μ°¨μˆ˜ 0인 μ •점듀을 κΈ°μ€€μœΌλ‘œ 1~3 κ³Όμ •을 λ°˜λ³΅ν•œλ‹€.

  λ§Œμ•½ 2번의 κ³Όμ •μ—μ„œ λ‹€μŒ 정점이 이미 λ°©λ¬Έν•œ 정점이라면 사이클이 λ°œμƒν•˜λŠ” κ·Έλž˜ν”„μ΄λ‹€. 즉, μœ„μƒ 정렬이 λΆˆκ°€λŠ₯ν•œ κ·Έλž˜ν”„μ΄λ‹€.

  μœ„μƒ 정렬이 κ°€λŠ₯ν•œ μ˜ˆμ‹œμ™€ μœ„μƒ 정렬이 λΆˆκ°€λŠ₯ν•œ μ˜ˆμ‹œλ₯Ό 톡해 λ‹€μ‹œ ν•œλ²ˆ μ•Œμ•„λ³΄μž. μ§„ν–‰ν•˜λ©΄μ„œ 방문체크λ₯Ό ν•΄μ£Όκ³  λŒμ•„μ˜€λ©΄μ„œ λ°©λ¬Έ 체크 ν•΄μ œν•˜λŠ” 것을 잘 κΈ°μ–΅ν•˜μž. 그러면 그림을 톡해 μ‰½κ²Œ 이해할 수 μžˆμ„ 거라고 μƒκ°ν•œλ‹€. 

[μœ„μƒ 정렬이 κ°€λŠ₯ν•œ μ˜ˆμ‹œ]

μœ„μƒ 정렬이 κ°€λŠ₯ν•œ κ·Έλž˜ν”„

1번 경둜 : 1 → 3 → 5 → 6

2번 경둜 : 1 → 4 → 6

3번 경둜 : 2 → 4 → 6

[μœ„μƒ 정렬이 λΆˆκ°€λŠ₯ν•œ μ˜ˆμ‹œ]

μœ„μƒ 정렬이 λΆˆκ°€λŠ₯ν•œ κ·Έλž˜ν”„

  사이클이 μ‘΄μž¬ν•˜λŠ” κ²½μš°λ„ μ‚΄νŽ΄λ³΄μž. μœ„μ—μ„œ μ–ΈκΈ‰ν•œ κ²ƒμ²˜λŸΌ λ°©λ¬Έ ν‘œμ‹œκ°€ λœ κ³³μ„ μž¬λ°©λ¬Έν•œλ‹€λ©΄ μ‚¬μ΄ν΄μ΄ λ°œμƒν•˜λŠ” κ²ƒμ΄λ‹€. 

 

5. DFSλ₯Ό μ΄μš©ν•œ κ΅¬ν˜„ μ½”λ“œ(JAVA) 🍷

  ν•΄λ‹Ή λ°©λ²•μ˜ μž…λ ₯ λ˜ν•œ μ •μ μ˜ κ°œμˆ˜μ™€ κ°„μ„ μ˜ 개수λ₯Ό λ°›μ•„μ˜€κ³ , κ°„μ„ μ˜ 개수만큼 좜발 정점과 도착 정점을 λ‚˜νƒ€λ‚Έλ‹€κ³  ν•˜μž. μ•„λž˜μ˜ μž…λ ₯은 μœ„μƒ 정렬이 λΆˆκ°€λŠ₯ν•œ κ·Έλž˜ν”„μ˜ μž…λ ₯이닀. (μœ„μƒ 정렬이 κ°€λŠ₯ν•œ κ·Έλž˜ν”„μ˜ μž…λ ₯은 BFSλ₯Ό μ΄μš©ν•œ κ΅¬ν˜„ μ½”λ“œμ—μ„œ μ°Έκ³ ν•  수 μžˆλ‹€.)

6 7
1 3
1 4
2 4
3 5
4 3
5 6
6 4
public class TopologySortingByDFS {
	static int N, M;
	static Node[] adjNode;
	static boolean[] visited;
	static boolean isCycle;
	static int[] inDegree;

	static class Node {
		int vertex;
		Node link;

		public Node(int vertex, Node link) {
			super();
			this.vertex = vertex;
			this.link = link;
		}
	}

	public static void main(String[] args) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(in.readLine(), " ");
		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());
		
		// 인접 리슀트
		adjNode = new Node[N + 1];
		// μ§„μž… 차수 - μΆœλ°œμ μ„ ν™•μΈν•˜κΈ° μœ„ν•œ 것
		inDegree = new int[N + 1];
		// λ°©λ¬Έ 체크 - 사이클 λ°œμƒ μ—¬λΆ€λ₯Ό ν™•μΈν•˜κΈ° μœ„ν•œ 것
		visited = new boolean[N + 1];
		int from, to;

		for(int i = 0; i < M; i++) {
			st = new StringTokenizer(in.readLine(), " ");
			from = Integer.parseInt(st.nextToken());
			to = Integer.parseInt(st.nextToken());
			// 인접 리슀트λ₯Ό λ§Œλ“€κΈ°
			adjNode[from] = new Node(to, adjNode[from]);
			// μ§„μž… 차수 μΆ”κ°€
			inDegree[to]++;
		}
		
		topologySortByDFS();

		if(isCycle) {
			System.out.println("μœ„μƒ μ •λ ¬ λΆˆκ°€λŠ₯ (사이클 λ°œμƒ)");
		}
		else {
			System.out.println("μœ„μƒ μ •λ ¬ κ°€λŠ₯");
		}
	}

	static void topologySortByDFS(){
		
		for(int i = 1; i <= N; i++) {
			if(inDegree[i] == 0) {
				visited[i] = true;
				dfs(i);
				visited[i] = false;
			}
		}
	}
	
	static void dfs(int current) {
		
		// λ§ˆμ§€λ§‰ 정점인 경우
		if(adjNode[current] == null) {
			return;
		}

		for(Node temp = adjNode[current]; temp != null; temp = temp.link) {
			// 사이클이 λ°œμƒν•˜λŠ” 경우
			if(visited[temp.vertex]) {
				isCycle = true;
				return;
			}
			visited[temp.vertex] = true;
			dfs(temp.vertex);
			visited[temp.vertex] = false;
		}
	}
}

 

6. μ‹œκ°„λ³΅μž‘λ„ πŸ§ƒ

  μœ„μƒ 정렬을 ν•  λ•Œ 'μ •μ μ˜ 개수 + κ°„μ„ μ˜ 개수'만큼 μ‹œκ°„μ΄ μ†Œμš”λœλ‹€. κ·ΈλŸ¬λ―€λ‘œ μœ„μƒ μ •λ ¬μ˜ μ‹œκ°„ λ³΅μž‘λ„λŠ” O(V  + E)이닀.

 

ν•΄λ‹Ή 글은
λ‚˜λ™λΉˆλ‹˜μ˜ μœ„μƒ μ •λ ¬(Topology Sort)
yoongrammerλ‹˜μ˜ μœ„μƒ μ •λ ¬(Topological sort) κ°œλ… 및 κ΅¬ν˜„
Topological Sorting using Kahn's Algorithm
을 μ°Έκ³ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.