포스트

백준 16947. 서울 지하철 2호선

알고리즘 기초 문제 중 601 - 그래프1(연습)

서울 지하철 2호선

1. 간단 설명

image1

image

지하철 노선도는 순환선과 지선으로 나눌 수 있다

순환선 : 한 역에서 출발해서 계속 가면 다시 출발한 역으로 돌아올 수 있는 노선

지선 : 순환선에 속하는 한 역에서 시작하는 트리 형태의 노선

즉 순환선은 사이클이고, 지선은 사이클에서 뻗어나온 트리

그래프에서 사이클을 찾고, 각 노드들과 이 사이클간의 거리를 구하는 문제

2. 예시

예제 1

1
2
3
4
5
4
1 3
4 3
4 2
1 2 
1
2
3
1 - 3
|   |
2 - 4

와 같은 순환선이므로, 모든 역은 순환선으로부터의 거리가 0이다

예제 2

1
2
3
4
5
6
7
6
1 2
3 4
6 4
2 3
1 3
3 5
1
2
3
4
1 - 2
ㄴ 3 - 4 - 6
   |
   5

1-2-3은 순환선이고, 4와 5는 거리가 1, 6은 거리가 2인 지선이다

3. 알고리즘

크게 두 파트로 나눌 수 있다

  1. 순환선을 찾는 부분

  2. 찾은 순환선과의 거리를 구하는 부분

순환선을 찾는 부분은 깊이 우선 탐색(DFS)를 통해서

깊이가 3이상이고, 시작점과 같은지를 비교하여 찾을 수 있다

예를들어 A - B - C - A 순환선이 있다면

A에서 A로 돌아왔을때 깊이는 3, 시작점인 A와 도착점인 A가 같으므로 순환선이다

A-B-A라면, 그냥 두 역이 연결되어있는것이므로 순환선이 아니다. 깊이는 3을 초과해야함

찾은 순환선과의 거리를 구하는 부분은

순환선의 역 번호를 기록해두고, 이를 너비 우선 탐색(BFS)를 돌린다

해당 역과 연결된 역들을 거리를 1씩 증가 시켜가면서, 모든 역을 탐색한다

1
2
3
4
5
6
7
8
1. N과 엣지 정보 정보 입력받기
2. 1번 역부터 시작하여 그래프에서 각 노드를 탐색하면서, 사이클을 찾는다
	2-1. 역을 탐색하면서, 이전 역의 정보를 저장한다
	2-2. 사이클을 찾았다면, DFS를 종료한다.
	- 사이클 정보는 visited에 저장되어있다
3. 사이클을 찾았다면, 이 사이클에 속한 노드들을 큐에 넣는다
4. 큐를 BFS를 돌려 각 노드의 거리를 기록한다
5. 기록된 거리를 출력한다

필요 변수

역의 개수 int N(3 ≤ N ≤ 3,000)

그래프 연결 정보 vector< vector<int> > edge

  1. 사이클을 찾는데 필요한 변수들

DFS를 사용할 것이므로 관련 변수들 필요

각 역에 방문했는지 체크할 int visited[3001] : 역에 방문하면서 어떤 역에서 왔는지를 기록해둔다

사이클을 찾았는지 기록할 플래그 bool cycle_found

  1. 각 역의 거리를 찾는데 필요한 변수들

BFS 탐색용 queue< pair<int, int> > qu;

BFS 탐색 동안 방문 여부 체크 및 거리를 기록할 int dist[MAX];

4. 소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include <iostream>
#include <vector>
#include <queue>

#define MAX 3001

using namespace std;

int N;
vector< vector<int> > edge;
int visited[MAX];
int dist[MAX];
bool cycle_found = false;


void input() {
	// freopen_s(new FILE*, "input.txt", "r", stdin);
	cin >> N;
	edge.resize(N + 1);
	for (int i = 0; i < N; i++) {
		int start, end; cin >> start >> end;
		edge[start].push_back(end);
		edge[end].push_back(start);
	}
}

void DFS(int level, int start, int node) {
	if (cycle_found == true) return;
	if (level > 2 && start == node) { // 3개 이상 지나왔고, 첫 노드와 현재 노드가 같다면 사이클임
		cycle_found = true;
		return;
	}

	// 현재 역과 연결된 역들 탐색
	for (int i = 0; i < edge[node].size(); i++) {
		int next = edge[node][i];

		if (visited[next] != 0) continue; // 이미 방문한 역이면 패스

		visited[next] = node; // 다음역 방문처리. 어느역에서 왔는지 기록
		DFS(level + 1, start, next); // 다음역으로 이동

		if (cycle_found == true) return; // 순환선을 찾은경우 visited에는 순환선 정보가 담겨있음
		visited[next] = 0; // 초기화
	}
}

void BFS() {
	queue< pair<int, int> > qu;
	
	// 순환선에 속하는 역들을 큐에 넣음
	for (int i = 1; i <= N; i++) {
		if (visited[i] != 0) {
			qu.push({ i, 1 });
			dist[i] = 1; // 순환선들은 모두 방문표시 해둠
		}
	}

	while (!qu.empty()) {
		pair<int, int> now = qu.front();
		qu.pop();

		int cur_node = now.first;
		int dis = now.second;

		// 현재 노드와 연결된 노드 탐색
		for (int i = 0; i <edge[cur_node].size(); i++) {
			int next = edge[cur_node][i];

			if (dist[next] != 0) continue; // 이미 방문한 노드
			dist[next] = dis + 1;
			qu.push({ next, dis + 1 });
		}
	}
}

int main() {
	input();

	// 1번 역부터, N번 역까지 돌면서 순환선 찾기
	for (int i = 1; i <= N; i++) {
		if (visited[i] == 0) {
			DFS(0, i, i);
		}
	}
	// 역은 모두 연결되어있으므로 1번 역만 살펴봐도 되지 않을까? 

	BFS();

	for (int i = 1; i <= N; i++) {
		cout << dist[i] - 1 << " "; // 거리를 1부터 시작했으므로 1씩 빼준다
	}
		
	return 0;
}

DFS와 BFS를 모두 사용하는 문제

두가지를 같이 사용하려하니 햇갈리는 부분이 많았다

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.