Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

5-dhlee777 #21

Merged
merged 3 commits into from
Apr 2, 2024
Merged

5-dhlee777 #21

merged 3 commits into from
Apr 2, 2024

Conversation

dhlee777
Copy link
Contributor

🔗 문제 링크

https://www.acmicpc.net/problem/1197

✔️ 소요된 시간

2시간

✨ 수도 코드

최소 스패닝(신장) 트리(mst)란 주어진 그래프의 모든 정점을 연결하는 부분그래프중에서 그 가중치의 합이 최소인 트리를 의미합니다.
신장트리

이 조건을 만족하기 위해서는
1.간선의 개수가 (정점-1)개 존재하여 한다.
2.사이클이 존재하지 않아야한다.(트리의 특성)

이 두가지 조건에 신경을 써서 문제를 풀었습니다.
저는 이 최소 신장 트리를 구하기 위해 그리디 방식을 이용하는 kruskal 알고리즘을 사용하였습니다. 이 알고리즘은 우선
주어진 간선들 중 우선순위 큐를 이용하여
1.최소 간선부터 뽑아낸다. (greedy)
2.그 간선을 추가했을때 사이클이 발생하는지 union-find를 통해 확인해준다.
3.if (사이클이 발생할경우) 반복문으로 돌아가서 큐에서 다시 간선을 뽑는다.
else(사이클 발생x) 그 간선을 카운트(추가)해주고 간선의 가중치를 sum 에 더해준다.
4. 만약 (간선의개수==정점-1)이 된다면 정점들이 다 연결된것이므로 종료한다.
5. 가중치의 합이 저장된 sum을 출력해준다.

#include<iostream>
#include<queue>
using namespace std;
int parent[10001];  //부모노드를 저장해주는 배열
int sum, cnt = 0;
int v_num, e_num;
priority_queue < pair<int, pair<int, int>>, vector<pair<int, pair<int, int>>>, greater<pair<int, pair<int, int>>>>q;
//간선의 가중치가 오름차순으로 정렬되는 우선순위큐,큐의 원소는pair(가중치,정점1,정점2)로 이루어진다.
int find_parent(int a) {   //어떤 노드의 루트노드를 찾는 함수
	if (a == parent[a]) return a;
	else
		return parent[a] = find_parent(parent[a]);
}
void add(int a, int b) {     //두 노드가 속한 집합을 합쳐주는 함수
	a = find_parent(a);
	b = find_parent(b);
	if (a > b) parent[a] = b;
	else parent[b] = a;
}
bool compare_union(int a, int b) {   //두 노드가 같은집합인지 판별해주는 함수
	a = find_parent(a);
	b = find_parent(b);
	return (a == b);
}
void find_min() {    //가중치의 최솟값을 출력하는 함수
	while (!q.empty()) {   //큐에서 간선의 가중치가 가장낮은 pair를 꺼낸다.(greedy)
		int v1 = q.top().second.first;      //연결된 정점1
		int v2 = q.top().second.second;    //연결된 정점2
		int eg = q.top().first;            //간선 가중치
		q.pop();
		if (compare_union(v1, v2)) continue;    //꺼낸 두 정점이 같은 집합일 경우 선택시 사이클이 발생하므로 선택하지않고 반복문 계속진행
		else   //두 정점이 다른 집합일경우(그 간선 선택)
		{
			add(v1, v2);  //두 정점 이 각각 속한 집합을 합쳐준다.
			sum += eg;   // 가중치를 더해준다.
			cnt++;   //간선을 카운트해준다
		}
		if (cnt == v_num - 1) //만약 간선의 수가 정점-1 이되면 정점들이 다 연결된것이므로 반복문 종료
			break;

	}
	cout << sum;  //가중치 의 합 출력
}
int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int v1, v2, eg;    // 정점1,정점2,가중치
	cin >> v_num >> e_num;
	for (int i = 0; i < e_num; i++) {   //간선 개수만큼 입력받는다
		cin >> v1 >> v2 >> eg;
		q.push(make_pair(eg, make_pair(v1, v2)));    //pair(가중치,정점1,정점2)를 큐에 넣는다. 
	}

	for (int i = 0; i < v_num; i++) {  //자기자신의 부모를 자기자신으로 초기화
		parent[i] = i;
	}
	find_min();
	return 0;
}

📚 새롭게 알게된 내용

실제로 자료구조 책에서 보기만 했던 최소 신장트리 인데 이렇게 직접 union-find를 이용해 사이클이 발생하는지 판별해주고 greedy 를 이용하여 (정점-1)개의간선을 찾아나가는 과정이 흥미로웠다. 이번에는 kruskal 알고리즘을 이용하였지만 다음번에는 prim 알고리즘을 이용하여 풀어봐야겠다.

Copy link
Collaborator

@InSange InSange left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최소 신장 트리 되게 오랜만에 보네요!
union find가 되게 유용하게 많이 쓰이는 군요.
최소 신장 트리에 대표적인 두가지 방법 prim크루스칼 두 가지 방법을 썼을 때 시간 복잡도가 궁금합니다! 추후에 업데이트 가능할까요?
저는 비교하고 업데이트 해주는 과정이 시간 복잡도 상으로 따졌을 때 우선 순위 큐 하나로 처리하는 prim이 빠른 것 같아서 prim으로 단번에 처리하는 경우가 많다보니 문득 궁금해지네요.

Copy link
Collaborator

@seongwon030 seongwon030 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유니온파인드를 전부터 잘 활용하시는 것 같아요! 크루스칼은 간선을 모두 확인하는 알고리즘인데 더 효율적인 알고리즘이 있는지 궁금합니당.

Copy link
Collaborator

@yuyu0830 yuyu0830 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

마침 알고리즘 수업에서 스패닝 트리를 배우고 있는데 딱 좋은 타이밍에 스패닝 트리를 풀어주셨네요. 덕분에 스패닝 트리 공부 충분히 하고 갑니다.
pari<int, pair<int, int>> 는 좀 복잡하니까 구조체나 class 사용하던가 typedef를 통해 좀 더 간단히 줄여서 사용할 수 있습니다 :)

@dhlee777
Copy link
Contributor Author

dhlee777 commented Apr 1, 2024

최소 신장 트리 되게 오랜만에 보네요! union find가 되게 유용하게 많이 쓰이는 군요. 최소 신장 트리에 대표적인 두가지 방법 prim크루스칼 두 가지 방법을 썼을 때 시간 복잡도가 궁금합니다! 추후에 업데이트 가능할까요? 저는 비교하고 업데이트 해주는 과정이 시간 복잡도 상으로 따졌을 때 우선 순위 큐 하나로 처리하는 prim이 빠른 것 같아서 prim으로 단번에 처리하는 경우가 많다보니 문득 궁금해지네요.

저는 prim 알고리즘으로 구현을 안해봐서 prim 시간복잡도는 찾아보니 정점의 개수가 n이고 간선의 개수가 e 일때 o(elogn)이라고 하네요..
크루스칼의 경우 간선들을 모두 우선순위큐에넣어 정렬시키는 알고리즘이므로 한번 삽입할때 loge 의 시간복잡도가 발생하므로 e개를 삽입하면 o(eloge) 의 시간복잡도가 됩니다. 따라서 간선이 많은경우는 프림 간선이 적은경우는크루스칼 알고리즘이 유리하다고 볼 수 있을거 같습니다 !

@dhlee777
Copy link
Contributor Author

dhlee777 commented Apr 1, 2024

유니온파인드를 전부터 잘 활용하시는 것 같아요! 크루스칼은 간선을 모두 확인하는 알고리즘인데 더 효율적인 알고리즘이 있는지 궁금합니당.

위에 언급한 대로 크루스칼의 시간복잡도는 o(eloge) 프림의 시간복잡도는 o(elogn)이므로 간선이 많을 경우에는 프림
간선이 적을경우에는 크루스칼의 알고리즘을 사용하는 등 상황에 맞게 효율적인 알고리즘을 사용해주시면 될 것
같아요 !

@dhlee777
Copy link
Contributor Author

dhlee777 commented Apr 1, 2024

마침 알고리즘 수업에서 스패닝 트리를 배우고 있는데 딱 좋은 타이밍에 스패닝 트리를 풀어주셨네요. 덕분에 스패닝 트리 공부 충분히 하고 갑니다. pari<int, pair<int, int>> 는 좀 복잡하니까 구조체나 class 사용하던가 typedef를 통해 좀 더 간단히 줄여서 사용할 수 있습니다 :)

감사합니다 ! 항상 저렇게 이중 pair를 사용했었는데 이제 구조체에 익숙해져봐야겠네요 !

@dhlee777 dhlee777 merged commit 1d7111c into main Apr 2, 2024
@dhlee777 dhlee777 deleted the 5-dhlee777 branch April 2, 2024 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants