위상 정렬 (topology sort)
2025. 1. 20. 17:09ㆍ알고리즘
위상 정렬은 사이클이 없는 방향 그래프에서 노드 순서를 찾는 알고리즘이다.
- 기능 : 노드 간의 순서를 결정
- 특징 : 사이클이 없어야 한다.
- 시간 복잡도 : O(V + E)
위상 정렬은 항상 유일한 값으로 정렬되지 않는다.
또한 사이클이 존재하면 노드 간의 순서를 명확하게 정의할 수 없으므로 위상 정렬을 적용할 수 없다.
순서를 찾는 알고리즘이므로 무방향 그래프에서는 적용할 수 없다.
위상 정렬의 원리
백준 2252: 줄 세우기
https://www.acmicpc.net/problem/2252
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> graph;
vector<int> inDegree;
vector<bool> visited;
vector<int> answer;
int main()
{
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int N, M;
cin >> N >> M;
graph.resize(N + 1);
inDegree.resize(N + 1);
visited.resize(N + 1, false);
for (int i = 0; i < M; i++)
{
int a, b;
cin >> a >> b;
graph[a].push_back(b);
inDegree[b]++;
}
//위상 정렬
while (true)
{
int cur;
//진입 차수가 0인 노드 찾기
for (int i = 1; i <= N; i++)
{
if (inDegree[i] == 0 && !visited[i])
{
cur = i;
//정답 배열에 넣기
answer.push_back(cur);
visited[i] = true;
break;
}
}
//모든 노드를 정렬한 경우 반복문 탈출
if (answer.size() == N)
break;
//0인 노드와 인접한 노드의 차수 1 내리기
for (int node : graph[cur])
{
inDegree[node]--;
}
}
for (int node : answer)
{
cout << node << " ";
}
return 0;
}
이 문제는 학생들을 노드로 하고 키의 비교를 간선으로 해서 그래프로 표현한 뒤 순서를 나타내는 것이므로 위상 정렬을 적용하면 되는 문제이다.
문제를 해석하고 적용할 알고리즘을 떠올리면 맞출 수 있을 것 같다.
지금 내 구현에서는 반복문을 돌면서 진입 차수가 0인 노드를 찾는데, 다른 풀이를 찾아보니까 BFS처럼 큐를 활용해서 깔끔하게 구현할 수 있었다.(방문 배열을 없앨 수 있음)
백준 1516번 : 게임 개발
https://www.acmicpc.net/problem/1516
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
vector<vector<int>> graph;
vector<int> inDegree;
vector<int> bulidTimes;
vector<int> answer;
int main()
{
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int N;
cin >> N;
graph.resize(N + 1);
inDegree.resize(N + 1, 0);
bulidTimes.resize(N + 1, 0);
answer.resize(N + 1, 0);
for (int i = 1; i <= N; i++)
{
cin >> bulidTimes[i];
while (true)
{
int data;
cin >> data;
if (data == -1)
break;
graph[data].push_back(i);
inDegree[i]++;
}
}
//위상 정렬
queue<int> Q;
for (int i = 1; i <= N; i++)
{
if (inDegree[i] == 0)
{
Q.push(i);
}
}
while (!Q.empty())
{
int cur = Q.front();
Q.pop();
for (int node : graph[cur])
{
inDegree[node]--;
//max를 하는 이유는 여러 선행 건물 중에 오래 걸린 시간을 기준으로 해야
//중첩된 선행 건물의 경우 시간이 무시되지 않을 수 있다.
answer[node] = max(answer[node], answer[cur] + bulidTimes[cur]);
if (inDegree[node] == 0)
{
Q.push(node);
}
}
}
for (int i = 1; i <= N; i++)
{
//자기 건물 짓는 시간 더하고 출력
answer[i] += bulidTimes[i];
cout << answer[i] << '\n';
}
return 0;
}
이 문제는 max 함수를 써야 한다는 아이디어가 없었어서 처음에 틀렸다.
선행 건물이 두 개 이상 있는 경우 max를 하지 않으면 더 적은 시간이 걸린 선행 조건의 건물의 시간이 정답 배열에 들어 갈 수 있으므로 max를 써서 큰 값을 넣어야 한다.
위상 정렬을 적용해야 할 수 있는 근거는 건물 짓는 순서가 존재하고 사이클이 생기지 않기 때문이다.(방향 비순환 그래프)
공부 자료 : Do it! 알고리즘 코딩테스트 C++ (김종관, 이지스 퍼블리싱)
'알고리즘' 카테고리의 다른 글
벨만-포드 알고리즘 (0) | 2025.01.23 |
---|---|
다익스트라 알고리즘 (0) | 2025.01.21 |
유니온 파인드(Union-Find) 알고리즘 (0) | 2025.01.19 |
그래프의 표현 (0) | 2025.01.18 |
유클리드 호제법(백준 1934, 1850) (0) | 2025.01.14 |