그래프와 관련된 알고리즘 중 여기서는 최소 신장 트리(Minimum Spanning Tree, 이하 MST)의 구현에 대해 살펴보겠다.
MST는 가중치가 있는 무방향 그래프의 한 부분 집합으로서,
그 특징은 그래프를 구성하는 가중치의 합이 최소인 연결 그래프(간선의 개수 == 정점의 개수 - 1) 라는 것 이다.
이러한 MST를 구하는 알고리즘에는 크게 두 가지가 있는데,
첫번째는 크루스칼 알고리즘이고, 두번째는 프림 알고리즘이다.
여기서는 먼저 크루스칼 알고리즘을 간략히 정리하고, 그 구현에 대해 살펴보겠다.
1. 간선을 가중치를 오름차순으로 정렬한다.
2. 남아있는 간선 중 최소 가중치의 간선을 선택
3. 해당 간선을 MST에 추가 시 사이클이 발생하면 제외, 발생하지 않으면 추가.
4. 간선의 개수 == 정점의 개수 - 1 ? goto 2 : goto exit;
다음은 그 구현에 대한 것이다.
static int graph[][8] = {
0, 6, 12, 0, 0, 0, 0, 0,
6, 0, 5, 0, 14, 0, 0, 8,
12, 5, 0, 9, 0, 7, 0, 0,
0, 0, 9, 0, 0, 0, 0, 0,
0, 14, 0, 0, 0, 0, 0, 3,
0, 0, 7, 0, 0, 0, 15, 10,
0, 0, 0, 0, 0, 15, 0, 0,
0, 8, 0, 0, 3, 10, 0, 0
};
static int mst[8][8];
static int parent[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
static bool edge_comp(pair<int, int> p1, pair<int, int> p2)
{
return graph[p1.first][p1.second] < graph[p2.first][p2.second];
}
static int __find_(int c)
{
while (parent[c] != c)
c = parent[c];
return c;
}
static int __find(int c)
{
stack<int> stk;
while (parent[c] != c)
{
stk.push(c);
c = parent[c];
}
while (!stk.empty())
{
parent[stk.top()] = c;
stk.pop();
}
return c;
}
static void __union(int e1, int e2)
{
int p1 = __find(e1);
int p2 = __find(e2);
parent[p2] = p1;
}
static bool is_cycle(int e1, int e2)
{
int p1 = __find(e1);
int p2 = __find(e2);
return p1 == p2;
}
static void MST()
{
vector<pair<int, int>> edges;
for (int y = 0; y < sizeof(graph[0]) / sizeof(int); y++)
{
for (int x = y + 1; x < sizeof(graph[0]) / sizeof(int); x++)
{
if (graph[y][x])
edges.push_back(pair<int, int>(y, x)); //0. 간선의 집합을 준비
}
}
sort(edges.begin(), edges.end(), edge_comp); //1. 간선을 오름차순으로 정렬
for (int i = 0, cnt = 0; i < edges.size() &&
cnt < sizeof(graph[0]) / sizeof(int) - 1; i++) //edge == vertex - 1
{
pair<int, int> &cur = edges[i];
if (is_cycle(cur.first, cur.second)) //2. cycle형성이 되면 추가 안하고
continue;
//2. cycle이 형성되지 않으면
cnt += 1;
mst[cur.first][cur.second] = graph[cur.first][cur.second]; //3. mst에 추가
mst[cur.second][cur.first] = graph[cur.first][cur.second]; //3. mst에 추가
__union(cur.first, cur.second); //두 정점을 하나의 집합으로 수정
}
}
먼저 언급할 점은 cycle의 발생 유무를 판별하는 것이다.
사이클 체크를 위해 union-find 라는 방식을 이용 할 수 있다.
자세한 내용은 https://gmlwjd9405.github.io/2018/08/31/algorithm-union-find.html 여기에서 참조하면 되겠다.
여기서 그 내용을 간단히 정리하면.
1. 최초에 모든 정점은 자신만의 집합을 갖게 된다.
2. 하나의 edge는 두 정점이 모여 만들어지기에, 해당 edge가 MST에 추가되면 두 정점은 하나의 집합으로 묶여야 된다. 이것이 union이다.
3. 두 정점이 같은 집합에 있게 되면, 해당 edge는 cycle을 형성하는 edge가 되는데, 이 때 사용하는 것이 find이다. find의 구현은 집합의 주인(혹은 루트)가 될 때 까지 위로 올라가는 형식이다. 이것이 __find_()이고.
이 방법은 정점이 많아지면 선형 시간이 걸릴 수 있게 되는데, 이를 개선한 것이 __find()이다.
여기서는 집합의 주인을 찾아 올라 갈 때 까지 거친 모든 정점들의 집합을 나중에 한꺼번에 수정한다.
크루스칼 알고리즘의 구현은 코드 내 주석을 참고하면 되고,
사용한 그래프는 아래와 같은 모습이며,
MST는 아래와 같은 모습이된다.
'알고리즘' 카테고리의 다른 글
같은 것이 있는 순열 (0) | 2021.10.28 |
---|---|
중복 조합 (0) | 2021.10.28 |
이진 탐색 (0) | 2021.10.27 |
Graph, DFS and BFS (0) | 2021.10.27 |
병합정렬 (0) | 2021.10.27 |