www.acmicpc.net/problem/13913

 

13913번: 숨바꼭질 4

수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 수빈이는 걷거나 순간이동을 할 수 있다. 만약, 수빈이의 위치가 X일

www.acmicpc.net

 

 

1. 풀이방법

 

- 걸리는 시간만 출력하라 했으면 정말 쉬웠을 텐데, 어떤식으로 경로도 출력 해야 합니다.

 

- 처음에는 출발점 1, 여기서 분화되는것이 3개 ( 배열:  0 | 1 2 3 )

 

- 그 다음 단계에서는 index 1,2,3 의 값(출발점에서 각각 3 개씩) ( 배열: 0 | 1 2 3 | 4 5 6 7 8 9 10 11 12 )

 

- 이런식으로 3의 제곱수로 분화된다는 것을 착안하여 k를 처음 발견한 위치의 index를 계산을 통해 앞쪽으로 이동

 

- 하면서 출력하였는데 시간이 너무 오래 걸립니다. (특히나 모든 경우의 수를 다 분화해서 가야하므로)

 

- 그래서 index계산을 통해서 가는 것을 포기하고 새로 bfs를 돌때 visit을 통해 방문한 지점은 다시 가지 않으므로

 

- 트리와 같은 느낌으로 어디서 왔는지 (부모가 누군지) 를 배열하나를 선언해서 넣어주면서 bfs를 돌고

 

- 나중에 k에서 시작해서 ---> 어디서왔는지를 쭉 탐색 ---> n을 발견하면 멈춤 이런식으로 구현하였습니다.

 

 

 

 

2. 주의사항

 

- 저 같은경우 출발점과 도착점은 무조건 출력해야 하는줄 알아서 

 

- 입력이 5 5 로 같으면 출력이 0 (시간) 5 5 (경로) 이렇게 인 것으로 착각 했으나

 

- n의 이동 경로이므로 5 하나만 출력해야 했습니다.

 

 

 

 

3. 나의코드

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

int n, k;
queue<int> tmpq;
bool visit[100001];
int from[100001]; //어디서 온 놈인지르 표시해주자
vector<int> resultarr;

int second = 0;

int bfs() {
	while (1) {
		int qsize = tmpq.size();
		while (qsize--) {
			int nextn = tmpq.front();
			tmpq.pop();
			if (nextn == k) { return second; }
			//1증가
			if (nextn + 1 <= 100000&& visit[nextn + 1] == false) {
				resultarr.push_back(2 * nextn);
				tmpq.push(nextn+1);
				visit[nextn + 1] = true;
				from[nextn + 1] = nextn;
			}

			if (nextn - 1 >= 0 && visit[nextn - 1] == false ) {
				tmpq.push(nextn - 1);
				resultarr.push_back(nextn - 1);
				visit[nextn - 1] = true;
				from[nextn - 1] = nextn;
			}
			
			if (nextn * 2 <= 100000&& visit[2 * nextn] == false) {
				tmpq.push(nextn *2);
				resultarr.push_back(nextn *2);
				visit[2 * nextn] = true;
				from[2 * nextn]=nextn;
			}
		}
		second++;
	}
}
void findparent() {
	int tmpk = k;
	resultarr.clear();
	resultarr.push_back(tmpk);
	while (1) {
		if (from[tmpk] == n) { resultarr.push_back(n); break; }
		resultarr.push_back(from[tmpk]);
		tmpk = from[tmpk];
	}
	for (int i = resultarr.size() - 1; i >= 0; i--) {
		cout<<resultarr[i] << " ";
	}
}


int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> k;
	int second = 0;
	if (n == k) { cout << 0 << "\n"; cout << n; return 0; }
	if (n > k) {
		cout << n-k<<"\n";
		cout << n << " ";
		for (int i = 1; i < (n - k); i++) {
			cout << n - i << " ";
		}
		cout << k << " ";
	}
	else {
		tmpq.push(n);
		visit[n] = true;
		cout << bfs() << "\n";
		findparent();
	}
	return 0;
}

 

'알고리즘 문제풀이 > DFS와 BFS' 카테고리의 다른 글

백준 1600 [C++]  (0) 2021.01.19
백준 12851 [C++]  (0) 2021.01.18
백준 19238 [C++]  (0) 2021.01.17
백준 17142 [C++]  (0) 2021.01.17
백준 16236 [C++]  (0) 2020.12.29

www.acmicpc.net/problem/2422

 

2422번: 한윤정이 이탈리아에 가서 아이스크림을 사먹는데

첫째 줄에 정수 N과 M이 주어진다. N은 아이스크림 종류의 수이고, M은 섞어먹으면 안 되는 조합의 개수이다. 아래 M개의 줄에는 섞어먹으면 안 되는 조합의 번호가 주어진다. 같은 조합은 두 번

www.acmicpc.net

1. 풀이방법 

 

- 1~N까지의 모든 아이스크림 중 3개를 만드는 모든 조합을 골라서

 

- 이게 가능한지 (최악의 조합에 속하는 것이 없는 지) 를 확인 후 방법의 수를 증가시켜 주면되는 문제였습니다.

 

- 조합을 만드는 것은 어렵지 않았고, 처음에는 최악의 조합을 <pair<int,int>>로 만들어

 

- 반복문을 통해서 psice() 가능한 아이스크림조합인지를 확인 했었는데, 시간초과가 발생하였습니다.

 

- 반복문 탐색 중 정지 조건 break 를 걸어 주고 난 뒤 제출해도 역시 시간초과 였습니다.

 

- 그래서 이차원배열을 선언하여 두 아이스크림이 최악의 조합인지를 O(1) 시간에 확인할 수 있도록

 

- 수정하여 제출 하였더니 통과 되었습니다.

 

- 그래프 구현방법 중 인접행렬 방식( 두 노드가 인접했는지 확인하는 것이 매우 빠르다라는 장점)

 

- 으로 부터 나온 생각으로 구현하였습니다.

 

 

 

2. 주의사항

 

- 문제 자체는 쉬웠지만, 시간을 단축하는 부분에 있어 특이점이 있는 것 같아 포스팅으로 남깁니다.

 

 

 

3. 나의코드 

 

- 주석 부분은 반복문을 통한 탐색 부 입니다.

 

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

int N;
int M;
int t1, t2;
// vector<pair<int, int>> worstice;
bool wi[201][201];
vector<int> icearr;
int pscase = 0;

/*bool psice() {
	for (int i = 0; i < worstice.size(); i++) {
		int tmp = 0;
		for (int j = 0; j < icearr.size(); j++) {
			if (icearr[j] == worstice[i].first || icearr[j] == worstice[i].second) {
				tmp++;
			}
			if (tmp == 2) return false;
			if (icearr[j] > worstice[i].second) break;
		}
	}
	return true;
}*/
bool ps() {
	if (wi[icearr[0]][icearr[1]] == true || wi[icearr[0]][icearr[2]] == true
		|| wi[icearr[1]][icearr[2]] == true) return false;
	else return true;
}

void makeice(int index,int cnt) {
	if (cnt == 3) {
		if (ps()) {
			pscase++;
		}
		return;
	}
	for (int i = index; i <= N; i++) {
		icearr.push_back(i);
		makeice(i+1,cnt + 1);
		icearr.pop_back();
	}
}

int main() {
	ios_base::sync_with_stdio(false);
	cout.tie(0); cin.tie(0);
	cin >> N >> M;
	for (int i = 0; i < M; i++) {
		cin >> t1 >> t2;
		// worstcase.push_back({t1,t2});
		wi[t1][t2] = true;
		wi[t2][t1] = true;
	}
	makeice(1,0);

	cout << pscase << "\n";

	return 0;
}

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

백준 15659 [C++]  (0) 2021.01.15
백준 18511 [C++]  (0) 2021.01.15
백준 9663 [C++]  (0) 2021.01.13
백준 1107 [C++]  (0) 2020.12.14
백준 14500 [C++]  (0) 2020.12.05

www.acmicpc.net/problem/2644

 

2644번: 촌수계산

사람들은 1, 2, 3, …, n (1≤n≤100)의 연속된 번호로 각각 표시된다. 입력 파일의 첫째 줄에는 전체 사람의 수 n이 주어지고, 둘째 줄에는 촌수를 계산해야 하는 서로 다른 두 사람의 번호가 주어진

www.acmicpc.net

1. 풀이방법

 

- 촌수를 계산하는 문제입니다 (두 정점 사이에)

 

- dfs를 통해서 계산을 하였고 간선의 가중치 (촌수가 1씩 증가)가 1이므로 bfs로도 쉽게 표현 가능할 것으로 보입니다.

 

- 두 정점이 인접했는지를 확인하는 연산이 주로 쓰이므로 인접행렬방식으로 그래프를 구현하였습니다.

 

 

 

2. 주의사항

 

- 딱히없음, 특별한 조건도 없고..

 

 

3. 나의코드

#include<iostream>
#include<string>
#include<array>
#include<vector>
#include<algorithm>
using namespace std;

int n, m;
int target1, target2;
bool connected[101][101];
bool visited[101];
bool suc;

void inputs() {
	cin >> n >> target1 >> target2;
	cin >> m;
	int tmp1, tmp2;
	for (int i = 0; i < m; i++) {
		cin >> tmp1 >> tmp2;
		connected[tmp1][tmp2] = true;
		connected[tmp2][tmp1] = true;
	}
}

void findchon(int t,int cnt) {
	visited[t] = true;
	if (t == target2) { cout << cnt << "\n";
	suc = true; return;
	}
	for (int i = 1; i <= n; i++) {
		if (i != t) {
			if (connected[t][i] && !visited[i]) {
				findchon(i, cnt + 1);
			}
			if (suc) break;
		}
	}
}


int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	inputs();
	findchon(target1,0);
	if (suc == false) cout << -1 << "\n";

	return 0;
}

'알고리즘 문제풀이 > DFS와 BFS' 카테고리의 다른 글

백준 17142 [C++]  (0) 2021.01.17
백준 16236 [C++]  (0) 2020.12.29
백준 7569 [C++]  (1) 2020.12.20
백준 1697 [C++]  (0) 2020.12.20
백준 16397 [C++]  (0) 2020.12.18

www.acmicpc.net/problem/17471

 

17471번: 게리맨더링

선거구를 [1, 4], [2, 3, 5, 6]으로 나누면 각 선거구의 인구는 9, 8이 된다. 인구 차이는 1이고, 이 값보다 더 작은 값으로 선거구를 나눌 수는 없다.

www.acmicpc.net

1.풀이 방법.

 

 

- 분류는 dfs로 하였다. (dfs를 통해서 연결된 같은 선거구를 탐색하였고)

 

 

- 선거구 1의 첫번째 구역을 기준으로 dfs를 한번 돌리고 , 선거구2의 첫번째 구역을 기준으로 dfs를 한번 수행했다.

 

 

- 그랬을때 방문을 체크하는 배열 check의 각 구역은 모두 true로 되어있어야 각 선거구가 모두 연결되어 있다는

뜻이다. (전 그렇게 체크하였습니다.)

 

 

- 처음에 조합을 만들어서(단 수의 제한이 없음 N개의 지역을 모두 선거구 1이 가져가는 것만 거르면 됨)

CASE를 만들어서 계산에 들어갔고 들어가서 선거구 2에 선거구1에 포함되지 않은 구역들을 넣어주어서

 

 

- 일단 CASE에 대한 선거구1,선거구2를 나누어 주었고 DFS를 돌려 연결되어있는지 확인후.

 

 

- 나누어진 선거구가 각자 모두 연결되어있다는 것이 확인되면 인구수의 합의 차이를 결과벡터에

삽입 해주고 소팅 후 최소값을 출력하였습니다.

 

 

 

 

 

2. 주의사항

 

 

- 전략을 처음에 세우고 들어갔는데..... 노트와 펜으로 세운 전략은 이랬습니다.

 

 

-<노트에 쓴 내용>

1. CASE를 만들자(분류) 

2. DFS로 각 CASE가 유효한 CASE인지 확인하자(모두 각자 연결되어 있는지)

3. 인구수 차이를 측정

 

 

- 이렇게 세우고 코드를 작성하러 들어갔는데

 

 

- 순차적으로 짜다보니 코드가 매우 길어졌습니다.

 

 

- 그리고 전역변수에 대한 각 작업 수행시 초기화가 필요한 경우 추가로 함수를 작성하고 하다보니..

  더 길어졌는데.....전략 뿐만아니라 조금 더 명확한 틀을 설정하려고 노력해야겠습니다.....

(코드가 길어지면 짜다 보면 막 헷갈리는 부분이 생기고 갑자기 멍해지기도 하고....;;)

 

 

 

 

 

3.나의코드

 

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

bool check[11]; //방문한 노드인지 확인
int groupindex[11]; //CASE별 각 구역이 어떤 선거구에 포함되어있는지 확인
vector<int> edges[11]; //연결된 간선 정보 저장
int peoplecount[11]; //각 구역별 인구수 저장변수
int tmp;
int N;
vector<int> votecamp1; //선거구1에 해당하는 구역을 담을 벡터
vector<int> resultmin;//각 CASE별 인구 수 차이를 담아놓을 변수

void input() {//입력받는 함수
	cin >> N;
	for (int i = 1; i <= N; i++) {
		cin >> peoplecount[i];
	}
	for (int i = 1; i <= N; i++) {
		cin >> tmp;
		for (int j = 0; j < tmp; j++) {
			int n; cin >> n;
			edges[i].push_back(n);
		}
	}

}
int getsum(vector<int> arr) { //인구수 합 구하는 함수
	int s = 0;
	for (int i = 0; i < arr.size(); i++) {
		s += peoplecount[arr[i]];
	}
	return s;
}
void resetcheck() { //초기화함수
	for (int i = 0; i < 11; i++) {
		check[i] = false;
	}
}
int gap(int num1, int num2) { //단순히 차이를 리턴하는 함수
	if (num1 > num2) { return (num1 - num2); }
	else { return (num2 - num1); }
}
void dfs(int index,int g) { //DFS수행
	check[index] = true;
	for (int i = 0; i < edges[index].size(); i++) {
		if (check[edges[index][i]]==false&&groupindex[edges[index][i]] == g) {
			//방문하지 않았고, 나와 같은 선거구 인 경우 탐색을 수행
			dfs(edges[index][i],g);
		}
	}
	return;
}
bool all() { //모두 방문했는지 체크를 위한 함수
	for (int i = 1; i <= N;i++) {
		if (check[i] == false) return false;
	}
	return true;
}
void groupindexreset() { //초기화 함수
	for (int i = 0; i < 11; i++) {
		groupindex[i] = 1;
	}
}
void checkvotecamp() {
	if (votecamp1.size() == N) { return; }
	vector<int> votecamp2; //선거구2를 담을 변수
	groupindexreset(); //각 지역이 어떤 선거구에 속했는지를 담을 변수 초기화
	for (int i = 1; i <= N; i++) { //1에 속하지 않은 지역은 2에 넣기
		bool c = false;
		for (int j = 0; j < votecamp1.size(); j++) {
			if (votecamp1[j] == i) { c = true; break; }
		}
		if (c == false) { votecamp2.push_back(i);
		groupindex[i] = 2;
		}
	}
	resetcheck();
	dfs(votecamp1[0],1); //선거구의 지역이 모두 연결되어있는지 체크를 위해 DFS
	dfs(votecamp2[0],2);
	if (all() == true) {
		int first = getsum(votecamp1);
		int second = getsum(votecamp2);
		resultmin.push_back(gap(first, second));
	}

}
void makecase(int num) { //조합CASE(수가 정해지지 않은)
	if (num > N) { return; }
	for (int i = num; i <= N; i++) {
		votecamp1.push_back(i);
		checkvotecamp();
		makecase(i + 1);
		votecamp1.pop_back();
	}
}
int main() {
	// 1 ~ N 의 구역
	input(); //입력
	makecase(1); //케이스 만들자
	sort(resultmin.begin(), resultmin.end()); //최소값 출력 부
	if (resultmin.empty()) { cout << -1; return 0; }
	cout << resultmin[0];
	return 0;
}

 

'알고리즘 문제풀이 > DFS와 BFS' 카테고리의 다른 글

백준 16234[C++]  (0) 2020.10.25
백준 18405 [C++]  (0) 2020.10.25
백준 18352 [C++]  (0) 2020.10.17
백준 2486 : 안전영역  (0) 2020.03.22
백준 2583  (0) 2020.03.02

0.Transitive Closure

- 그래프 G가 주어졌을 떄 G*은 G와 같은 정점집합(same vertices)을 가진다.

- 만약 그래프 G가 정점 u에서 v로 경로를 가지면 그래프 G*는 정점 u에서 v로 간선(edge)를 가진다.

- Transitive Closure은 경로가 존재하는 지 여부를 알 수 있다.

- 물론 모든 정점에서 DFS를 수행하면 O(n(n+m)) --링크드리스트로 구현되어있다면

  에 알아낼 수 있다.

 

1. 개요

- 플로이드 워셜 알고리즘은 그래프에서 Transitive Closure을 구하거나

  n:n(다대다) shortest path를 구하는 데 사용 된다.

- Dynamic Programming 알고리즘이다.

- "A에서 B로 갈 수 있고 B에서 C로 갈 수 있는데 AC간선이 없다면, 간선을 연결해준다."

 

 

2.아이디어

- 모든 정점들을 번호로 관리한다.

- n단계로 구성되어 차례차례 확장해나가는 구조.

- k번째 단계의 수행이 완료되면, 1~k까지의 간선들은 모두 활용한 것 이다.

- 친구들끼리 연락을 해야하는 상황을 생각해보면 이해가 쉽다.

   (난 A의 전화번호를 모르지만 B의 번호를 알고있다. B는C의 번호를알고있다.

     그럼 난 두 단계를 거치면 C의 번호를 알게된다.)

 

 

3.The Floyd-Warshall Algorithm(플로이드 워셜 알고리즘)

- 각 단계의 그래프를 G0,G1,.......Gn이라 하자.

- 정점들은 v1,v2,.....,vn이라 한다.

- k단계에서 Gk는 Gk-1으로 부터 계산한다.

- 수행시에 adjacent연산을 많이 쓰는데 (두 정점 사이에 경로가 있는지 u->v)

   그럼 Graph를 구현 할 때 인접행렬로 구현하는 게 좋겠죠~

- 전화번호 예시에 빗대어 설명하였습니다.

-그럼 n:n shortes path는 어떻게 구하느냐??
- 기본적인 원리는 위와 같습니다만, 새로 알게된 경로가 기존의 경로보다 짧을 경우에 그 값을 넣어주고

   그렇지 않은 경우는 update하지 않습니다.

- 처음 초기화를 무한대로 해줍니다.

 

 

4.Example

(1)

(2)

(3)

(4)

(5)

 

5. 분석(Analysis)

- n:n shortest path를 구할 때 n번의 다익스트라 알고리즘을 호출하면 O(nmlogn)의 시간 복잡도를 가진다.

- 플로이드 워셜 알고리즘은 O(n*3)의 시간 복잡도를 가진다.

 

0. 시작에 앞서...

- 만약, 그래프가 가중치가 모두 같은 간선들로 이루어져 있다면 BFS(넓이 우선탐색)을 통하여 쉽게

   최단거리를 구할 수 있다.

- 오늘은 가장 유명한 최단거리 구하는 알고리즘인 다익스트라 알고리즘에 대하여 알아보겠다.

 

 

1. Shortest-Path 정의

- 그래프 G=(V,E,W)에서 P는 nonempty-path이라 하자

- W(P) (Weight of P) = the sum of the weights (각 간선들의 가중치의 합)  이다.

- 만약 x=y (즉 같은 정점) 사이의 가중치는 0 이다.

- x정점과 y정점사이에 W(P)보다 가중치가 작은 path가 없다면 이 경로가

    shortest-path or minimum-weight path이다

 

 

2.Properties of Shortest Paths

- Lemma: shortest path property

- 간단하게 말하면 최단경로는 최단경로들로 구성되어있다 라는 것.

- 이는 귀류법으로 손쉽게 증명할 수 있다.

- if) P가 최단경로가 아니거나 Q가 최단경로가 아니라면(즉, 위 그림처럼 P'가 존재)

           W(P')<W(P)이고 W(P')+W(Q)<W(P)+W(Q) 즉 R이 최단경로가 아니다.

 

3. Dijkstra's Algorithm(다익스트라 알고리즘)

- Weight are nonnegative (가중치는 음수가 아니여야한다.)

- greedy 알고리즘 이기도 하며 dynamic programming 알고리즘 이기도 하다.

- 기본적인 구조는 prim 알고리즘과 비슷하다.

 

 

4.Example

- 가중치가 같다면 알파벳 순서로 선택한다고 가정.

(1) A-출발점

(2) 가중치가 가장 짧은 간선 2를 선택 결과 테이블 UPDATE

(3) AG(3)을 선택

(4) BC(4)를 선택

(.....) 이런식으로 테이블을 업데이트 해서 끝나면 A점으로 부터 다른 정점들로의

최단거리를 테이블에서 불러오기만 하면 된다.

 

 

5.Correctness

- 새로운 경로가 나타날 수 도 있는데 최단경로를 이렇게 확정해버려도 되는가?

- 간선의 가중치는 음수가 아니므로 새로운 경로가 나타도 같을 순 있어도, 

   더 짧은 경로가 나타날 순 없다.

 

6.분석(Analysis)

- 힙자료구조를 이용한 우선순위 큐 사용시 시간복잡도는 O(mlogm) = O(mlogn)

이라 할 수 있습니다.

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