061 가장 빠른 버스 노선 구하기
왜 플로이드 워셜임?
→ 시작점이 주어지지 않았고 모든 정점 쌍 간의 최단 경로 구하는 문제임
→ N이 최대 100임
“시작 도시와 도착 도시를 연결하는 노선은 1개가 아닐 수 있다” → 중복 간선이 존재하면 최소 비용으로 갱신해야 함
⇒ if (distance[s][e] > v) distance[s][e] = v;
**(3, 5, 1), (3, 5, 10)으로 주어지면 1로 초기화하여 시작한다는 말임~
//처음에 아래 처럼 했다가 예외처리 못해서 틀렸음
distance[i][j] = Integer.MAX_VALUE;
...
if(distance[i][j] > distance[i][k] + distance[k][j])
distance[i][j] = distance[i][k] + distance[k][j];
⇒distance[i][k] 또는 distance[k][j]가 Integer.MAX_VALUE인 경우, 즉 갈 수 없는 경로인데도 덧셈 연산이 수행되면 오버플로우가 발생함
예: Integer.MAX_VALUE + 5 == 음수 (overflow)
그래서 아래처럼 예외처리 해 주던가
if (distance[i][k] != Integer.MAX_VALUE && distance[k][j] != Integer.MAX_VALUE) {
if (distance[i][j] > distance[i][k] + distance[k][j]) {
distance[i][j] = distance[i][k] + distance[k][j];
}
}
아니면 distance[i][j] = 10000001;처럼 충분히 큰 수로 초기화 **최악의 경우 거리 합이 “100 × 100,000 = 10,000,000”이니까
062 경로 찾기
왜 플로이드 워셜인가?
→ N이 100이하에서 눈치까쓰(N^3 쌉 가능)
→ 모즌 정점 쌍에 대해 경로 존재 여부를 알고 싶음
→ 입력도 출력도 0/1 기반 인접 행렬임
이 문제에선 경로가 존재하는지 아닌지만 판단하면 됨
⇒ S와 E가 모든 중간 경로(K) 중 1개라도 연결되어 있다면 S와 E는 연결 노드로 저장
for (int k = 0; k < N; k++) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (distance[i][k] == 1 && distance[k][j] == 1)
distance[i][j] = 1;
}
}
}
063 케빈 베이컨의 6단계 법칙
왜 플로이드 워셜?
→ N이 최대 100
→ 모든 정점 쌍의 최단 거리 합임
**BFS로도 가능하지만 각 정점마다 1회씩 BFS 수행이 필요하므로 좀 더 복잡하다고 함
플로이드 워셜로 distance[i][j] = i에서 j까지 최단 거리 모두 구한 후, 모든 i에 대해 distance[i][1] + distance[i][2] + ... + distance[i][N]을 계산해서 가장 작은 값을 가지는 i를 출력하면 끝!
최소 신장 트리
그래프의 모든 정점을 연결하면서 사용된 에지들의 가중치의 합이 최소가 되도록 만드는 트리 구조
사이클이 없어야 하고 에지의 수는 N-1
크루스 칼과 프림 알고리즘이 존재
크루스 칼
에지 리스트 만들기
모든 에지을 가중치 기준 오름차순 정렬
사이클 안 생기게 연결 (유니온 파인드 배열 초기화)
**유니온 파인드가 사이클 판별을 위해 사용함
<aside>
find(x): x가 속한 **집합의 대표(parent)**를 찾음
→ 어떤 노드가 어떤 집합(그룹)에 속하는지를 알려면
→ 그 노드의 최종 대표(root)를 알아야 함
→ 바로 그걸 찾는 게 find(x)의 역할!
union(x, y): x와 y가 속한 두 집합을 하나로 합침 </aside>
에지 개수가 (N - 1)개 되면 종료
064 최소 신장 트리 구하기
그냥 MST 구현할 수 있냐 물어보는 문제임
우선순위 큐 써서 자동 정렬해서 하면 됨
PriorityQueue는 Comparable이 구현된 객체만 사용 가능
→ 그래서 compareTo() 필수
<aside>
주의) 자바에서 PriorityQueue를 쓸 때 Comparable을 구현하거나, Comparator를 넘겨주는 방식 중 하나는 꼭 필요!
</aside>
065 다리 만들기
import java.util.*;
public class P17472_다리만들기 {
static int N, M;//지도 세로, 가로
static int[][] map;//지도(0이 바다, 1이 땅)
static boolean[][] visited;//섬 구분할 때 방문했는지 체크용
static int islandNum = 2; //섬번호 2부터 시작
static List<Edge> edgeList = new ArrayList<>();//다리후보목록
static int[] parent;//대표 노드
//상하좌우 방향 벡터
static int[] dx = {-1,1,0,0}; //행 읻ㅇ
static int[] dy = {0,0,-1,1};//열 이동
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
N=sc.nextInt();
M=sc.nextInt();
map=new int[N][M];
visited = new boolean[N][M];
//지도 정보 입력 받고
for(int i =0; i< N; i++) {
for(int j = 0; j<M; j++)
map[i][j] = sc.nextInt();
}
//bfs로 섬마다 고유 번호 부여
for(int i = 0; i<N; i++) {
for(int j=0; j<M; j++) {
if(!visited[i][j] &&map[i][j] == 1)
bfs(i, j, islandNum++);
}
}
//각 섬에서 상하좌우로 다리 연결 가능한지
for(int i =0; i<N; i++) {
for(int j = 0; j<M; j++) {
if(map[i][j] >= 2)//섬이면
findBridge(i, j);
}
}
//MST
parent = new int[islandNum];
for(int i = 2; i< islandNum; i++)
parent[i] = i;
Collections.sort(edgeList);//다리 가중치 기준 정렬
int result = 0;
int count = 0; //사용된 다리 개수
for(Edge e : edgeList) {
if(union(e.from, e.to)){//서로 다른 섬이면 연결
result += e.len;
count++;
}
}
//연결된 섬 수가 부족하면 -1
if(count == islandNum -3)
System.out.println(result);
else
System.out.println(-1);
}
//bfs로 섬 구분하고 번호 부여
static void bfs(int x, int y, int num) {
Queue<int[]> q = new LinkedList<>();
q.add(new int[] {x, y});//현재 좌표 {x, y}를 큐에 저장
visited[x][y] = true;
map[x][y] = num; //고유 번호 부여
while(!q.isEmpty()) {
int[] cur = q.poll();//큐에서 현재 좌표 꺼내서 4방향 하나씩 확인
for(int d = 0; d<4; d++) {
int nx = cur[0] + dx[d];
int ny = cur[1] + dy[d];
//다음 좌표 (nx, ny)가 범위 안에 있고 방문 안했고 현재 섬이면(1) 방문 표시하고 큐에 추가
if(inRange(nx, ny)&& !visited[nx][ny] && map[nx][ny] ==1 ) {
visited[nx][ny] = true;
map[nx][ny] = num;//같은 섬 번호 부여
q.add(new int[] {nx, ny});
}
}
}
}
//범위 체크
static boolean inRange(int x, int y) {
return x>=0 && y>= 0&& x<N && y< M;
}
//한 칸에서 상하좌우로 다리 뻗는
static void findBridge(int x, int y) {
int from = map[x][y];//시작 섬 번호
for(int d = 0; d<4; d++) {
int nx = x;
int ny = y;
int len = 0;//다리 길이
//직선으로 쭉
while(true) {
nx += dx[d];// 다음 칸으로 이동 (행)
ny += dy[d];// 다음 칸으로 이동 (열)
if(!inRange(nx, ny)) break; //범위밖이면 종료
if(map[nx][ny] == from) break; //자기섬이면 종료?
if(map[nx][ny] == 0) {//바다면 킵고잉
len++;
}else {
//다른섬 만나면
if(len >= 2) {
edgeList.add(new Edge(from, map[nx][ny], len));
}
break;
}
}
}
}
static int find(int x) {
if(parent[x] == x) return x;
return parent[x] = find(parent[x]);
}
static boolean union(int a, int b) {
int pa = find(a);
int pb = find(b);
if(pa == pb) return false;
parent[pa] = pb;
return true;
}
static class Edge implements Comparable<Edge>{
int from, to, len;
Edge(int from, int to, int len) {
this.from = from;
this.to = to;
this.len = len;
}
@Override
public int compareTo(Edge o) {
return this.len - o.len; // 오름차순 정렬
}
}
}
방향 벡터
static int[] dx = {-1, 1, 0, 0}; // 위, 아래, 좌, 우 static int[] dy = {0, 0, -1, 1};
d | dx[d] | dy[d] | 의미 |
---|---|---|---|
0 | -1 | 0 | 위로 한 칸 (x-1, y) |
1 | +1 | 0 | 아래로 한 칸 (x+1, y) |
2 | 0 | -1 | 왼쪽 한 칸 (x, y-1) |
3 | 0 | +1 | 오른쪽 한 칸 (x, y+1) |
→ cur = [2, 3] 일 때