www.acmicpc.net/problem/2887

 

2887번: 행성 터널

첫째 줄에 행성의 개수 N이 주어진다. (1 ≤ N ≤ 100,000) 다음 N개 줄에는 각 행성의 x, y, z좌표가 주어진다. 좌표는 -109보다 크거나 같고, 109보다 작거나 같은 정수이다. 한 위치에 행성이 두 개 이

www.acmicpc.net

 

1. 풀이방법

 

- 최소신장트리 mst 를 구하는 문제이다.

 

- 크루스칼 알고리즘을 떠올려서 구현을 했으나 첫 제출결과.....메모리초과.....

 

- 이 문제에서 사실 크루스칼 알고리즘  자체를 구현하는 것은 매우 쉬웠으나

 

- 제출 이후 문제 조건을 파악해보니 행성의 수 N개 의 범위가 100,000이다.

 

- 첫 구현은 모든 간선 (n*(n-1))/2 를 모두 넣고 sorting을 돌렸으니 메모리 초과가 뜰 수 밖에.....

 

- 그래서 후보가 될 수 있는 간선 자체를 줄이는 방법을 만들어야 했는데, 그 답은

 

- min(|x1-x2|,|y1-y2|,|z1-z2|) 여기에 있었다.

 

- 일단 1차원을 가정하고 말해보자 행성이 1,5,6,7,24,36에 있다

 

- 위치 1에서의 모든 간선은 (1,5), (1,6), (1,7), .... (1,36)까지 인데 이를 모두 볼필요 없이

 

- 정렬된 상태에서 맞닿은 정점들을 연결하면 이것이 1차원에서의 mst이다 (1-5-6-7-24-36)

 

- 그렇다면 3차원에서는....? 이렇게 해도 되는건가 머리가 아팠는데...

 

- 일단 결론은 된다는 것. |x1-x2|에 대해서 정렬후 맞닿은 간선들을 모두 구해서 edge에 넣고

 

- y와 z에 대해서도 이 작업을 했다.

 

- 그리고 이 간선을 cost를 기준으로 오름차순 정렬을 해주는데

 

- 차원을 나눠서 계산을 해도 되는 이유는 예를 들자면 7-24 이 두 행성사이의 x값 차이는 17인데

 

- 만약 y 나 z 값의 차이에서 더 작은 차이가 있다면 그 간선도 edge 벡터내에 있을 것이고 오름차순 정렬

 

- 했으므로 먼저 살펴보게 되므로 상관이 없다 뒤에 7-24를 연결하는 간선은 이미 두 정점이 같은 집합 내에 있다면

 

- 그냥 무시하고 넘어가기 떄문, 그리고 y나 z에서 두 행성이 붙어있지 않다면??? 이라는 의문이 들었는데

 

- 이 역시 붙어있지 않아 직접적인 간선은 없다고 하더라도 그 사이의 다른 정점과 연결된 간선을 통해서 7-24를 연결

 

- 하는 간선을 살펴보기 전에 이미 같은 집합에 포함되게 된다.

 

- 이를 구현한 후 제출했는데 결과는 시간초과.....흠....ㅆ..

 

- 첫 구현한 코드의 문제점은 그냥 planet 구조체에 각각 key(즉 root) 값을 넣어서 집합이 합쳐질 경우

 

- 바꿔야할 key값을 가진 정점들을 모두 탐색 해서 바꿔 주게끔 되어있었다.

 

- 이렇게 되면 후보 간선들을 모두 보는 for문 안에서 또 다른 for문(정점을 모두 탐색하는)

 

- 이 있었는데 입력이 크니 시간 초과가 날 수 밖에...

 

- 그래서 결국 key(root) 를 저장하고 있는 vector를 따로 선언해 재귀를 통해 탐색하고 집합의 key값이 바뀌어야 할 

 

- 경우 탐색해서 찾은 key값만 바꿔주면 되게끔 구현 하였다.

 

- 여기서의 key(root)값의 의미는 집합의 대표값이다(1번 집합,2번집합...)

 

 

 

2. 주의사항

 

- 수식을 통해서 간선의 수를 줄일 수 있는 아이디어를 생각하는 것.

 

 

- 입력의 규모를 잘 파악하고 구현을 시작할 것.

 

 

 

3. 나의코드

 

#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;

int N;
int resultsum;

struct planet { int x, y, z,idx; };
struct edges { int cost, index1, index2; };

vector<planet> parr;
vector<int> rootindex;
vector<edges> earr;


bool cmp(edges a, edges b) {
	if (a.cost < b.cost) return true;
	else return false;
}
bool cmpx(planet a, planet b) {
	if (a.x < b.x) return true;
	else return false;
}
bool cmpy(planet a, planet b) {
	if (a.y < b.y) return true;
	else return false;
}
bool cmpz(planet a, planet b) {
	if (a.z < b.z) return true;
	else return false;
}

int minthree(int n1, int n2, int n3) {
	return min(min(n1, n2),n3);
}

void inputs() {
	cin >> N;
	int tmpx, tmpy, tmpz;
	planet tmpp;
	for (int i = 0; i < N; i++) { //정점삽입
		cin >> tmpx >> tmpy >> tmpz;
		tmpp.x = tmpx; tmpp.y = tmpy; tmpp.z = tmpz; tmpp.idx = i;
		parr.push_back(tmpp);
		rootindex.push_back(i); //처음에 루트는 각자 자기자신
	}
}
int find(int r) {
	if (rootindex[r] == r) return r;
	else { return rootindex[r] = find(rootindex[r]); }
}
void makeedges() { //간선 삽입
	edges tmpe;
	sort(parr.begin(), parr.end(), cmpx); //x 기준으로 오름차순 정렬
	for (int i = 0; i < N-1; i++) {
		tmpe.cost = abs(parr[i].x-parr[i + 1].x);
		tmpe.index1 = parr[i].idx; tmpe.index2 = parr[i + 1].idx;
		earr.push_back(tmpe);
	}
	sort(parr.begin(), parr.end(), cmpy); //y 기준으로 오름차순 정렬
	for (int i = 0; i < N - 1; i++) {
		tmpe.cost = abs(parr[i].y-parr[i + 1].y);
		tmpe.index1 = parr[i].idx; tmpe.index2 = parr[i + 1].idx;
		earr.push_back(tmpe);
	}
	sort(parr.begin(), parr.end(), cmpz); //z 기준으로 오름차순 정렬
	for (int i = 0; i < N - 1; i++) {
		tmpe.cost = abs(parr[i].z-parr[i + 1].z);
		tmpe.index1 = parr[i].idx; tmpe.index2 = parr[i + 1].idx;
		earr.push_back(tmpe);
	}
	sort(earr.begin(), earr.end(), cmp); //cost기준으로 오름차순 정렬
}
void makeMST() {
	int exitcount = 0;
	for (int i = 0; i < earr.size(); i++) {
		if (exitcount == N - 1) break;
		int firstplanet = find(earr[i].index1);
		int secondplanet = find(earr[i].index2);
		if (firstplanet == secondplanet) continue;
		rootindex[secondplanet] = firstplanet;
		resultsum += earr[i].cost;
		exitcount++;
	}
}


int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(NULL); cout.tie(NULL);
	inputs();
	makeedges();
	makeMST();
	cout << resultsum;
	return 0;
}

'알고리즘 문제풀이 > 정렬' 카테고리의 다른 글

백준 11652 [C++]  (0) 2021.01.26
백준 1620 [C++]  (0) 2021.01.09
백준 10825 [C++]  (0) 2020.10.25
백준 10814  (0) 2020.03.02
백준 1181  (0) 2020.01.15

0. 시작에 앞서...

- Minimum-Spanning-Tree 문제는 그래프의 최적화 문제 중 유명한 하나의 문제이다.

- 모든 정점을 최소비용으로 연결할 수 있는 트리를 구하거나 그 비용을 알아내는 문제.

- 즉 최적해(optimal solution)을 찾아내는 문제.

- 최소신장트리를 구하는 유명한 알고리즘으로 Prim과 Kruscal이 있는데 이번 시간에는 Kruscal 알고리즘을 다룬다.

 

 

1. 최소신장트리

최소 신장 트리( MST )란, 주어진 그래프에서 최소한의 비용으로 트리를 만드는 것

- 최소신장트리는 무향연결그래프에서 정의되며, 즉 connected, undirected,weight graph.

- 그래프의 weight는 그래프를 이루고 있는 subgraph들의 간선 가중치의 합.

- 트리 이므로 당연히 사이클이 발생해서는 안된다.

 

 

2. Union-Find data structure

- 서로 교집합이 없는 집합들을 모아놓은 자료형 ( disjoint set )

- operation : find(u) -> set id를 통해서 집합을 구분하는데 u라는 노드를 포함하고 있는 집합의 set id 를 return 한다.

                 union(u,v) -> u,v 두 노드가 각각 포함된 두 집합을 합집합한다.

 

3.Kruskal's Algorithm

- 접근 방식이 prim과 약간다르다고 할 수 있다.

- greedy technique을 이용한 알고리즘

- 각 노드들을 tree로 만들어 forest를 구성하고 그 tree들을 합쳐나간다.(merge)

- 간선에 dependent하다.

- 1. 가장 가중치가 적은 간선을 선택.

  2. 간선에 인접한 두 노드에 대해서 각각 find 연산을 수행한다.

  3. find(u) 와 find(v)의 리턴값이 다르면 두 노드가 서로다른 set에 포함되어있었던 것 이므로

     간선의 가중치를 추가해주고 두 노드에 대해 union(u,v)연산을 수행한다.(합집합)

  4. 만약 리턴값이 같다면 이미 두 노드는 같은 집합에 포함되어 있으므로 다른 작업을 수행하지 않는다.

  5. 모든 노드들이 포함되어 하나의 tree가 되면 위의 작업들을 반복하던 것을 종료한다.

 

4. Example

(1)

(2)

(3)

(!! 집합의 SET ID는 집합의 노드 중 알파벳 순으로 가장 작은것이 SET id 라고 가정)

 

(4)

(5)

(6)

(7)

(8)

(9)

(10)

(11)

(12) 모든 노드가 TREE에 포함되었다.(MST)

5. 분석(Analysis)

- 시간복잡도 O(mlogm)  // m:간선의 수

- 크루스칼은 간선에 dependent하므로 그래프가 희소(sparse)

- 즉, 간선이 별로 존재하지 않는 그래프에서는 프림보다는 크루스칼이 유리하다.

- 시간복잡도, 즉 성능의 경우는 union-find 자료구조(data structure)을 어떻게 구현하느냐에 따라

   매우 상이하다.

- union find ADT를 구현할 때 

   weighted union : 더 큰 집합 뒤쪽에 작은 집합을 붙임

   path-compression: find연산을 한번 수행할 시 뒤쪽에 달린 노드들이 모두 setid인 노드를 가리키도록 구조를 바꿈

 이러한 방법들을 사용한다면 약간의 성능 향상을 기대할 수 있다.

'알고리즘 > 최소신장트리(MST)' 카테고리의 다른 글

Prim's Algorithm(프림 알고리즘)  (0) 2020.10.09

0. 시작에 앞서...

- Minimum-Spanning-Tree 문제는 그래프의 최적화 문제 중 유명한 하나의 문제이다.

- 모든 정점을 최소비용으로 연결할 수 있는 트리를 구하거나 그 비용을 알아내는 문제.

- 즉 최적해(optimal solution)을 찾아내는 문제.

- 최소신장트리를 구하는 유명한 알고리즘으로 Prim과 Kruscal이 있는데 이번 시간에는 prim의 알고리즘을 다룬다.

 

 

1. 최소신장트리

- 최소 신장 트리( MST )란, 주어진 그래프에서 최소한의 비용으로 트리를 만드는 것

- 최소신장트리는 무향연결그래프에서 정의되며, 즉 connected, undirected,weight graph.

- 그래프의 weight는 그래프를 이루고 있는 subgraph들의 간선 가중치의 합.

- 트리 이므로 당연히 사이클이 발생해서는 안된다.

 

 

2.프림알고리즘의 아이디어

- 프림알고리즘은 트리를 점점 키워나가는 알고리즘이다.

- 먼저, 임의의 starting vertex를 선택한다.( 보통 그것이 root가 된다.)

- 연결된 간선 중 가중치가 작은 정점들을 차례대로 붙여나간다.

- 정점(vertices)들은 3종류의 disjoint categories로 나누어지는데

  1. Tree vertices

  2. Fringe vertices

  3. Unseen vertices

- 맨 처음에 모든 정점들은 unseen vertices이고 fringe set을 거쳐서 tree set으로 들어가게 된다.

  (단, 처음 선택하는 root vertex만이 fringe set을 거치지 않고  tree set에 들어가게 된다.)

- 예시로 한번 살펴보자(Example)

(1) 그래프는 아래와 같고 A라는 점을 root로 선택해서 시작한다고 가정.

(2) root를 제외한 모든 정점들은 unseen으로 setting

A에서 인접한 정점들을 fringe set에 넣어준다.

(3) (2)에서 가장 가중치가 적은 B를 선택해서 tree set에 넣고 다시 트리 정점으로 부터 인접한

     정점들을 fringe set에 넣는다. B와 인접해있는 C가 추가되었고..

     간선이 중복되는 경우가 있는데 이 경우 선택이 필요하다.

     AG(3) VS BG(6) 인데 AG가 가중치가 더 적으므로 AG를 선택한다.

(4) 이와 같은 과정을 모든 정점을 포함할 때 까지 하는 것이다.

     각 과정에서 가장 가중치가 적은 간선에 연결된 정점을 선택한다는 점에서 그리디 라고도 할 수 있겠다.

     FRINGE SET 은 Min Priority Queue를 이용하면 편하다.

 

 

3. Prim's Algorithm

 

4. 분석 (Analysis)

- 어떤 구조로 fringe set을 구현하느냐에 따라 차이가 있다.

- 일단, 모든 정점이 fringeset을 거쳐 tree set에 하나씩 들어가므로 (정점의 개수를 n이라 하면)

- unsorted array로 구현시 O(n^2)이라고 할 수 있다. 

- 힙으로 구현하면 fringe set에 삽입하는 시간을 O(log n)으로 줄일 수 있다.

- 프림 알고리즘의 경우 정점 개수에 dependent하므로 

- 그래프가 dense(간선이 많을 경우) 할 경우 프림의 알고리즘이 유리하다.

+ Recent posts