테마 선택

📚 지식 노트

AI·하드웨어·주변기기·게임·컴퓨터 시스템·이론 컴퓨터 과학·수학·개발과 운영·보안·응용 분야에 대해 알아 두면 좋은 내용을 정리한 노트다. 위에서 분야를 고르고 아래에서 노트를 고른다.

그래프 문제

개념마다 확인(뜻을 맞게 알았나) → 추적(손으로 한 단계씩) → 응용(처음 보는 상황) 순서로 푼다. 풀이 보기를 누르기 전에 종이에 먼저 적어 보자.

목차 (9)
  1. 인접 행렬·인접 리스트
  2. BFS
  3. DFS
  4. 위상 정렬
  5. 다익스트라
  6. 벨먼-포드
  7. 플로이드-워셜
  8. 유니온 파인드
  9. 크루스칼·프림

인접 행렬·인접 리스트

확인

아래 방향 그래프를 인접 행렬과 인접 리스트로 적어라. 점마다 진입 차수와 진출 차수는 행렬의 어디에서 읽는가?

방향 그래프: A→B, A→C, B→D, D→E, E→B, C→F, F→EABCDEF
점 여섯, 간선 일곱의 방향 그래프.
풀이 보기

행렬은 아래와 같고, 리스트는 A: B, C / B: D / C: F / D: E / E: B / F: E다. 진출 차수는 행의 합, 진입 차수는 열의 합이다.

6×6 인접 행렬: 줄마다 출발점에서 나가는 간선이 있는 칸이 1A011000B000100C000001D000010E010000F000010
인접 행렬. 행이 출발, 열이 도착이다. 방향 그래프라 대칭이 아니다.

열 순서는 A~F다. B와 E 열에 1이 둘씩 있어 진입 차수가 2이고, A 행에 1이 둘이라 A의 진출 차수가 2다. 칸 36개 가운데 1은 7개뿐이다. 리스트는 간선 7개만 적으니 이런 희소 그래프에서 훨씬 작다.

추적

방향 없는 그래프의 인접 행렬이 아래와 같다. 그래프를 그리고, 점마다 차수와 간선 수를 구하라.

5×5 대칭 인접 행렬A01100B10010C10010D01101E00010
열 순서도 A~E다.
풀이 보기

차수는 A 2, B 2, C 2, D 3, E 1이고, 간선은 5개다.

간선 A-B, A-C, B-D, C-D, D-E인 그래프, 점 옆에 차수A2B2C2D3E1
행렬이 뜻하는 그래프. A에서 두 갈래로 나뉘었다가 D에서 만나고 E로 이어진다.

차수는 행(또는 열)의 1 개수다. 간선 하나가 양 끝 두 점의 차수를 하나씩 올리므로 차수의 합은 간선 수의 두 배다(2 + 2 + 2 + 3 + 1 = 10 = 2 × 5). 행렬에서도 1이 10개, 간선마다 대칭으로 두 칸이다.

응용

사용자 100만 명, 한 사람당 평균 친구 100명인 친구 관계 그래프를 메모리에 담는다. 인접 행렬(칸마다 1비트)과 인접 리스트(이웃 하나에 4바이트)의 크기를 어림하라.

크기 비교: 인접 행렬은 칸이 V²개, 인접 리스트는 이웃 항목이 2E개인접 행렬V × V 칸인접 리스트V + 2E 항목V = 1,000,000, E = 50,000,000
같은 그래프를 담는 두 방법의 크기(눈금 없이 비율만).
풀이 보기

인접 행렬은 약 125GB, 인접 리스트는 약 400MB다.

행렬은 V² = 10¹²칸이고 1비트씩이면 10¹² / 8 = 1.25 × 10¹¹바이트, 125GB다. 친구 관계는 방향이 없어 간선 E = 100만 × 100 / 2 = 5,000만 개이고, 리스트는 간선마다 양쪽에 한 번씩 2E = 1억 항목 × 4바이트 = 400MB다(점마다 리스트 시작 위치 몇 MB가 더 든다). 칸의 99.99%가 0인 행렬은 낭비다. 실제 그래프는 이렇게 희소하므로 대부분 리스트를 쓴다.

BFS

확인

BFS를 짜면서 방문 표시를 "큐에 넣을 때"가 아니라 "큐에서 꺼낼 때" 했다. 아래 그래프에서 A부터 돌리면 무슨 일이 생기는가? 이웃은 글자 순으로 본다.

간선 A-B, A-C, B-D, C-D, D-E인 그래프ABCDE
A에서 갈라진 두 길이 D에서 만난다.
풀이 보기

D가 큐에 두 번 들어간다. B를 꺼낼 때 D를 넣고, C를 꺼낼 때도 D가 아직 "방문 안 함"이라 또 넣는다. 큐에 들어가는 횟수가 5번에서 6번으로 는다.

BFS 단계 그림: 넣을 때 방문 표시하면 D는 B에서 한 번만 큐에 들어간다A거리 0BCDEAA거리 0B거리 1C거리 1DEBCA거리 0B거리 1C거리 1D거리 2ECDA거리 0B거리 1C거리 1D거리 2EDA거리 0B거리 1C거리 1D거리 2E거리 3EA거리 0B거리 1C거리 1D거리 2E거리 3(비었음)A거리 0B거리 1C거리 1D거리 2E거리 3(비었음)큐 앞 →다 본 점지금 꺼낸 점큐에서 기다리는 점BFS 트리 간선
1 / 7

시작점 A, 방문 표시하고 큐에 넣는다.

올바른 BFS. 넣을 때 표시하므로 C를 꺼낼 때 D는 이미 방문한 점이라 다시 넣지 않는다.

이 그래프에서는 한 번 더일 뿐이지만, 여러 길이 한 점으로 모이는 격자 같은 그래프에서는 같은 점이 큐에 차수만큼 들어가 메모리와 시간이 몇 배로 는다. 넣을 때 표시하면 점마다 정확히 한 번 들어가 O(V + E)가 보장된다.

추적

아래 그래프에서 D부터 BFS를 돌린다(이웃은 글자 순). 큐에서 꺼내는 순서와 점마다의 거리(간선 수)를 적어라.

일곱 점 그래프: A-B, A-C, B-C, B-D, C-F, D-E, D-F, E-G, F-GABCDEFG
점 일곱, 간선 아홉의 무가중치 그래프.
풀이 보기

꺼내는 순서는 D, B, E, F, A, C, G이고, 거리는 D 0, B·E·F 1, A·C·G 2다.

BFS 단계 그림: D에서 시작해 거리 1인 B, E, F, 거리 2인 A, C, G 순으로 방문한다ABCD거리 0EFGDAB거리 1CD거리 0E거리 1F거리 1GBEFA거리 2B거리 1C거리 2D거리 0E거리 1F거리 1GEFACA거리 2B거리 1C거리 2D거리 0E거리 1F거리 1G거리 2FACGA거리 2B거리 1C거리 2D거리 0E거리 1F거리 1G거리 2ACGA거리 2B거리 1C거리 2D거리 0E거리 1F거리 1G거리 2CGA거리 2B거리 1C거리 2D거리 0E거리 1F거리 1G거리 2GA거리 2B거리 1C거리 2D거리 0E거리 1F거리 1G거리 2(비었음)A거리 2B거리 1C거리 2D거리 0E거리 1F거리 1G거리 2(비었음)큐 앞 →다 본 점지금 꺼낸 점큐에서 기다리는 점BFS 트리 간선
1 / 9

시작점 D, 방문 표시하고 큐에 넣는다.

D에서 시작한 BFS. 거리 1인 점을 모두 꺼낸 뒤에야 거리 2로 넘어간다.

F를 꺼낼 때 이웃 C, D, G는 이미 모두 큐에 들어갔거나 방문해 새로 넣을 것이 없다. 거리 2인 A, C, G를 꺼낼 때도 마찬가지다. 굵은 선 여섯 개(점 수 − 1)가 BFS 트리다.

응용

격자 미로에서 S에서 G까지 상하좌우로 한 칸씩 움직인다. 어두운 칸은 벽이다. 가장 적게 움직이는 횟수는? 이 문제를 그래프로 보면 점과 간선은 무엇인가?

5줄 7칸 미로. 왼쪽 위 S, 오른쪽 아래 G, 벽이 세로로 여러 줄 막고 있다SG
5×7 격자 미로.
풀이 보기

14번이다. 점은 벽이 아닌 칸, 간선은 상하좌우로 맞닿은 두 칸이다. 간선 가중치가 모두 1이니 BFS가 그대로 최단 거리를 준다.

칸마다 S에서의 거리를 적은 미로. G의 거리는 14S012891013711245612371345678G14
S에서 BFS로 잰 칸마다의 거리. 아래쪽 길은 5번 열의 벽에 막혀, 위로 돌아 오른쪽 끝 열을 타고 내려온다.

그래프를 따로 만들 필요 없이 "(r, c)의 이웃은 네 방향 중 벽이 아닌 칸"으로 그때그때 계산하는 암묵적 그래프다. 아래쪽으로 가면 G 근처(거리 8)까지 빨리 가지만 벽에 막혀 막다른 길이다. BFS는 모든 방향을 한 겹씩 넓히므로 이런 막다른 길에 속지 않는다. 게임에서 "이번 턴에 갈 수 있는 칸" 표시가 바로 이 거리 표다.

DFS

확인

아래 그래프에서 A부터 DFS를 돌리면 발견 순서는 어떻게 되는가(이웃은 글자 순)? 같은 그래프에서 A부터 BFS를 돌린 순서와 비교하라.

일곱 점 그래프: A-B, A-C, B-C, B-D, C-F, D-E, D-F, E-G, F-GABCDEFG
BFS 문제와 같은 일곱 점 그래프.
풀이 보기

DFS는 A, B, C, F, D, E, G이고, BFS는 A, B, C, D, F, E, G다.

DFS 단계 그림: A에서 B, C, F, D, E, G로 한 줄로 파고든 뒤 차례로 돌아온다ABCDEFG(비었음)A1/BCDEFGAA1/B2/CDEFGABA1/B2/C3/DEFGABCA1/B2/C3/DEF4/GABCFA1/B2/C3/D5/EF4/GABCFDA1/B2/C3/D5/E6/F4/GABCFDEA1/B2/C3/D5/E6/F4/G7/ABCFDEGA1/B2/C3/D5/E6/F4/G7/8ABCFDEA1/B2/C3/D5/E6/9F4/G7/8ABCFDA1/B2/C3/D5/10E6/9F4/G7/8ABCFA1/B2/C3/D5/10E6/9F4/11G7/8ABCA1/B2/C3/12D5/10E6/9F4/11G7/8ABA1/B2/13C3/12D5/10E6/9F4/11G7/8AA1/14B2/13C3/12D5/10E6/9F4/11G7/8(비었음)A1/14B2/13C3/12D5/10E6/9F4/11G7/8(비었음)호출 스택완료한 점지금 점스택에 걸린 점DFS 트리 간선
1 / 16

글자 순으로 시작점을 고른다. 시각은 1부터 센다.

A에서 시작한 DFS. 점 옆은 발견/완료 시각이다. 이 그래프에서는 한 줄로 끝까지 파고든다.

DFS는 B에 들어가자마자 B의 첫 이웃 C로, 다시 C의 이웃 F로 파고든다. 그래서 A에서 간선 하나 거리인 C를 세 번째에 보지만, F는 A에서 2인데 네 번째, D는 2인데 다섯 번째다. 발견 순서가 거리 순이 아니므로 DFS 트리의 길(A-B-C-F-D-E-G)은 최단 경로가 아니다. 최단 거리가 필요하면 BFS다.

추적

아래 방향 그래프에서 글자 순으로 DFS를 돌려 점마다 발견/완료 시각을 적어라. 역방향 간선(아직 완료되지 않은 조상으로 가는 간선)은 무엇이고, 그것이 알려 주는 사이클은? F → E는 역방향 간선인가?

방향 그래프: A→B, A→C, B→D, D→E, E→B, C→F, F→EABCDEF
점 여섯, 간선 일곱의 방향 그래프.
풀이 보기

A 1/12, B 2/7, D 3/6, E 4/5, C 8/11, F 9/10. 역방향 간선은 E → B이고 사이클 B → D → E → B를 알려 준다. F → E는 역방향 간선이 아니다.

DFS 단계 그림: A, B, D, E로 파고들고 E에서 스택 위의 B를 만난다ABCDEF(비었음)A1/BCDEFAA1/B2/CDEFABA1/B2/CD3/EFABDA1/B2/CD3/E4/FABDEA1/B2/CD3/E4/5FABDA1/B2/CD3/6E4/5FABA1/B2/7CD3/6E4/5FAA1/B2/7C8/D3/6E4/5FACA1/B2/7C8/D3/6E4/5F9/ACFA1/B2/7C8/D3/6E4/5F9/10ACA1/B2/7C8/11D3/6E4/5F9/10AA1/12B2/7C8/11D3/6E4/5F9/10(비었음)A1/12B2/7C8/11D3/6E4/5F9/10(비었음)호출 스택완료한 점지금 점스택에 걸린 점DFS 트리 간선
1 / 14

글자 순으로 시작점을 고른다. 시각은 1부터 센다.

E에 있을 때 B는 아직 스택에 걸려 있다(완료 전). 그 B로 가는 간선이 사이클을 닫는다.

E를 볼 때 스택은 A, B, D, E라 E → B는 진행 중인 조상으로 돌아가는 간선이다. 반면 F를 볼 때 E는 이미 시각 5에 완료됐다. F → E는 다른 갈래에서 끝난 점으로 건너가는 교차 간선이라 사이클이 아니다. 그래서 방향 그래프의 사이클 검사는 "방문했나"가 아니라 "스택에 걸려 있나(완료 전인가)"를 봐야 한다.

응용

아래 방향 없는 그래프에 연결 요소(서로 오갈 수 있는 점의 묶음)가 몇 개인지, DFS로 어떻게 세는지 설명하라.

그래프: A, B, C 삼각형, D, E, G, F 사각형, 혼자인 HABCDEFGH
점 여덟, 간선 일곱.
풀이 보기

연결 요소는 3개({A, B, C}, {D, E, F, G}, {H})다. 모든 점을 글자 순으로 보며 아직 발견되지 않은 점에서 DFS를 새로 시작한 횟수가 곧 요소 수다.

DFS 단계 그림: A, D, H에서 세 번 새로 시작한다ABCDEFGH(비었음)A1/BCDEFGHAA1/B2/CDEFGHABA1/B2/C3/DEFGHABCA1/B2/C3/4DEFGHABA1/B2/5C3/4DEFGHAA1/6B2/5C3/4DEFGH(비었음)A1/6B2/5C3/4D7/EFGHDA1/6B2/5C3/4D7/E8/FGHDEA1/6B2/5C3/4D7/E8/FG9/HDEGA1/6B2/5C3/4D7/E8/F10/G9/HDEGFA1/6B2/5C3/4D7/E8/F10/11G9/HDEGA1/6B2/5C3/4D7/E8/F10/11G9/12HDEA1/6B2/5C3/4D7/E8/13F10/11G9/12HDA1/6B2/5C3/4D7/14E8/13F10/11G9/12H(비었음)A1/6B2/5C3/4D7/14E8/13F10/11G9/12H15/HA1/6B2/5C3/4D7/14E8/13F10/11G9/12H15/16(비었음)A1/6B2/5C3/4D7/14E8/13F10/11G9/12H15/16(비었음)호출 스택완료한 점지금 점스택에 걸린 점DFS 트리 간선
1 / 18

글자 순으로 시작점을 고른다. 시각은 1부터 센다.

새 시작점이 A, D, H로 세 번 나온다. 스택이 빌 때마다 요소 하나를 다 본 것이다.

한 번의 DFS는 시작점에서 닿는 점을 모두, 그리고 그것만 방문한다. 그래서 시작할 때마다 요소 하나가 통째로 칠해진다. 점과 간선을 한 번씩 보므로 전체 O(V + E)다. BFS로 해도 똑같고, 간선이 하나씩 추가되는 상황이면 다음 절의 유니온 파인드가 더 알맞다.

위상 정렬

확인

아래 DAG의 위상 순서로 맞는 것을 모두 고르라. (가) A, C, B, E, F, D (나) C, A, E, B, D, F (다) A, B, C, E, F, D (라) A, B, D, C, E, F

DAG: C→E, A→E, A→B, E→F, B→F, B→D, F→DCAEBFD
노트의 위상 정렬 그림과 같은 DAG.
풀이 보기

(가)와 (다)가 맞다. (나)와 (라)는 D가 F보다 앞서 간선 F → D를 어긴다.

간선 F→D를 빨갛게 표시한 DAGCAEBFD
(나)를 어긴 간선 F → D를 빨간 점선으로 표시했다.

간선 7개마다 "출발이 도착보다 앞인가"를 보면 된다. 위상 순서는 여러 개일 수 있다. A와 C는 서로 관계가 없어 어느 쪽이 먼저여도 되고, (다)처럼 B를 C보다 먼저 해도 된다. 정해진 것은 간선이 잇는 쌍의 앞뒤뿐이다.

추적

일곱 작업의 선후 관계다(화살표는 "먼저 → 나중"). 칸 알고리즘으로 위상 정렬하라. 처음 큐는 글자 순으로 채우고, 꺼낸 점의 이웃은 글자 순으로 보며 진입 차수가 0이 된 점을 큐 뒤에 넣는다.

DAG: C→E, A→E, A→B, E→G, B→G, B→D, G→F, D→FCAEBGDF
일곱 작업의 DAG.
풀이 보기

A, C, B, E, D, G, F다.

위상 정렬 단계 그림: 진입 차수 0인 A, C부터 꺼내 A, C, B, E, D, G, F 순서를 만든다C진입 0A진입 0E진입 2B진입 1G진입 2D진입 1F진입 2ACC진입 0AE진입 1B진입 0G진입 2D진입 1F진입 2CBACAE진입 0B진입 0G진입 2D진입 1F진입 2BEACCAE진입 0BG진입 1D진입 0F진입 2EDACBCAEBG진입 0D진입 0F진입 2DGACBECAEBG진입 0DF진입 1GACBEDCAEBGDF진입 0FACBEDGCAEBGDFACBEDGFCAEBGDFACBEDGF큐순서순서에 붙은 점지금 꺼낸 점큐에서 기다리는 점지운 간선
1 / 9

점마다 들어오는 간선 수(진입 차수)를 센다. 0인 A, C부터 큐에 넣는다.

칸 알고리즘. A를 꺼내면 B가, C를 꺼내면 E가 진입 차수 0이 된다.

처음 진입 차수가 0인 점은 A와 C다. A를 꺼내면 E는 2 → 1(C가 남음), B는 1 → 0이라 큐에 들어간다. 큐가 [C, B]라 다음은 C이고 E가 0이 된다. 이렇게 큐 순서를 따라가면 E보다 B가 먼저, G보다 D가 먼저 나온다. 큐를 우선순위 큐로 바꾸면 "가능한 것 중 글자가 가장 앞선 것"을 고르는 다른 순서(A, B, C, D, E, G, F)가 된다.

응용

빌드 도구가 아래 의존 관계로 칸 알고리즘을 돌렸다. 어떤 결과가 나오는가? 빌드 도구는 사용자에게 무엇을 알려야 하는가?

방향 그래프: A→B, A→C, B→D, D→E, E→B, C→F, F→EABCDEF
DFS 문제와 같은 방향 그래프.
풀이 보기

A, C, F까지만 나오고 큐가 빈다. 남은 B, D, E가 서로 물린 순환 의존(B → D → E → B)이라 "순환 의존이 있다"고 알리고 그 점들을 보여 줘야 한다.

위상 정렬 단계 그림: A, C, F를 꺼낸 뒤 진입 차수가 1씩 남은 B, D, E에서 멈춘다A진입 0B진입 2C진입 1D진입 1E진입 2F진입 1AAB진입 1C진입 0D진입 1E진입 2F진입 1CAAB진입 1CD진입 1E진입 2F진입 0FACAB진입 1CD진입 1E진입 1FACFAB진입 1CD진입 1E진입 1FACF큐순서순서에 붙은 점지금 꺼낸 점큐에서 기다리는 점지운 간선
1 / 5

점마다 들어오는 간선 수(진입 차수)를 센다. 0인 A부터 큐에 넣는다.

사이클이 있는 그래프. B, D, E는 서로를 기다리느라 진입 차수가 0이 되지 않는다.

F를 꺼내면 E의 진입 차수가 2 → 1로 줄지만 나머지 1은 사이클 안의 D에서 온다. 남은 점의 진입 차수가 모두 1 이상이면 그 점들 사이에 반드시 사이클이 있다. 여기서는 남은 점이 사이클과 정확히 같지만, 일반적으로는 사이클 뒤에 매달린 점도 함께 남으므로 정확한 고리는 DFS의 역방향 간선으로 찾아 보여 준다.

다익스트라

확인

아래 방향 그래프에는 음수 간선 B → A(−2)가 하나 있다. S에서 다익스트라를 돌리면 A와 T의 거리를 얼마로 답하는가? 참 최단 거리는?

방향 그래프: S→A 2, S→B 3, B→A −2, A→T 123-21SABT
음수 간선 하나가 있는 방향 그래프.
풀이 보기

다익스트라는 A 2, T 3으로 답하지만, 참 거리는 A 1(S → B → A), T 2다.

다익스트라 단계 그림: A가 거리 2로 먼저 확정되어, 뒤에 B를 거치는 더 짧은 길을 놓친다23-21S0A∞B∞T∞0, S23-21S0A2B3T∞2, A3, B23-21S0A2B3T33, B3, T23-21S0A2B3T33, T23-21S0A2B3T3(비었음)23-21S0A2B3T3(비었음)큐(작은 순)확정방금 꺼낸 점거리가 줄어든 점흐린 큐 항목: 확정된 점의 낡은 값
1 / 6

S 거리 0, 나머지는 ∞. 큐에 (0, S) 항목을 넣는다.

A가 2로 확정된 뒤 B를 꺼내 B → A를 보지만, 이미 확정된 점이라 고치지 않는다.

다익스트라는 "남은 점은 지금 꺼낸 점보다 멀거나 같으니 그 점을 거쳐 돌아와도 길어질 뿐"이라는 가정에 기댄다. 음수 간선은 멀리 돌아와도 짧아질 수 있게 해 이 가정을 깬다. 틀린 A 값은 A를 거치는 T까지 번진다. 음수 간선이 있으면 벨먼-포드를 쓴다.

추적

아래 그래프에서 S부터 다익스트라를 돌린다. 점이 확정되는 순서와 최단 거리, T까지의 최단 경로를 구하라. 큐에서 꺼냈다가 버리는 낡은 항목은 몇 개인가?

가중치 그래프: S-A 7, S-B 2, A-B 3, A-C 4, B-D 9, C-D 1, C-T 6, D-T 372349163SABCDT
점 여섯, 간선 여덟의 가중치 그래프.
풀이 보기

확정 순서는 S 0, B 2, A 5, C 9, D 10, T 13이고, T까지의 경로는 S → B → A → C → D → T다. 낡은 항목은 (7, A), (11, D), (15, T) 세 개다.

다익스트라 단계 그림: S에서 B, A, C, D, T 순으로 거리 2, 5, 9, 10, 13이 확정된다72349163S0A∞B∞C∞D∞T∞0, S72349163S0A7B2C∞D∞T∞2, B7, A72349163S0A5B2C∞D11T∞5, A7, A11, D72349163S0A5B2C9D11T∞7, A9, C11, D72349163S0A5B2C9D11T∞9, C11, D72349163S0A5B2C9D10T1510, D11, D15, T72349163S0A5B2C9D10T1311, D13, T15, T72349163S0A5B2C9D10T1313, T15, T72349163S0A5B2C9D10T1315, T72349163S0A5B2C9D10T13(비었음)72349163S0A5B2C9D10T13(비었음)큐(작은 순)확정방금 꺼낸 점거리가 줄어든 점흐린 큐 항목: 확정된 점의 낡은 값
1 / 11

S 거리 0, 나머지는 ∞. 큐에 (0, S) 항목을 넣는다.

S에서 시작한 다익스트라. 거리가 줄 때마다 새 항목을 넣고, 옛 항목은 꺼낼 때 버린다.

A는 처음 S에서 7로 들어가지만 B를 거치면 2 + 3 = 5라 줄어든다. D도 B에서 11로 들어갔다가 C를 거쳐 10, T도 C에서 15였다가 D를 거쳐 13이 된다. 줄어든 점마다 옛 항목이 큐에 남아 나중에 꺼낼 때 버려진다. 경로는 굵은 선(최단 경로 트리)을 T에서 거꾸로 따라가면 나온다.

확인

같은 그래프에서 S부터 T까지 "간선 수가 가장 적은 길"과 "가중치 합이 가장 작은 길"을 각각 구하라. 왜 다른가?

가중치 그래프: S-A 7, S-B 2, A-B 3, A-C 4, B-D 9, C-D 1, C-T 6, D-T 372349163SABCDT
앞 문제와 같은 그래프.
풀이 보기

간선이 가장 적은 길은 간선 3개(S → B → D → T, 무게 14 / S → A → C → T, 무게 17)이고, 가장 가벼운 길은 간선 5개인 S → B → A → C → D → T(무게 13)다.

가장 가벼운 길 S-B-A-C-D-T를 굵게, 간선 3개짜리 길의 B-D 구간을 빨간 점선으로 표시한 그래프72349163SABCDT
굵은 선이 가장 가벼운 길(13), 빨간 점선이 간선 3개짜리 길 가운데 가벼운 쪽(14)이다.

BFS는 간선을 모두 무게 1로 보고, 다익스트라는 실제 무게를 본다. 무게 9인 B-D를 피해 가벼운 간선 여럿으로 돌아가는 편이 합이 작다. 지도로 치면 "갈아타는 횟수가 적은 길"과 "가장 빨리 도착하는 길"이 다른 것과 같다. 문제가 무엇을 최소로 하는지에 맞춰 알고리즘을 고른다.

응용

같은 그래프에서 S와 T 두 곳에 소방서가 있다. 점마다 "가장 가까운 소방서까지의 거리"를 구하라. 다익스트라를 두 번 돌리지 않고 한 번에 구하는 방법은?

같은 가중치 그래프에서 S와 T를 소방서로 칠했다72349163SABCDT
채운 두 점이 소방서다.
풀이 보기

A 5, B 2, C 4, D 3(S·T는 0). 처음 큐에 S와 T를 둘 다 거리 0으로 넣고 다익스트라를 한 번 돌리면 된다.

점마다 가까운 소방서까지 거리: S 0, A 5, B 2, C 4, D 3, T 072349163S0A5B2C4D3T0
점 옆 숫자가 가장 가까운 소방서까지의 거리다. A, B는 S가, C, D는 T가 더 가깝다.

이는 새 점 Z를 하나 만들어 Z → S, Z → T에 무게 0인 간선을 단 뒤 Z에서 다익스트라를 돌리는 것과 같다(가상 출발점). 시간은 한 번 돌리는 O((V + E) log V) 그대로다. 소방서가 100곳이어도 마찬가지라, 100번 돌리는 것보다 훨씬 싸다. 게임의 "가장 가까운 적까지 거리" 지도도 이렇게 만든다.

벨먼-포드

확인

노트의 벨먼-포드 예제는 간선을 일부러 나쁜 순서로 훑어 4바퀴가 필요했다. 같은 간선을 S→A, S→B, B→A, A→C, B→D, C→D 순서로 훑으면 몇 바퀴째에 거리가 더 줄지 않는가? 결과 거리는 같은가?

방향 그래프: S→A 6, S→B 2, B→A 3, A→C 4, B→D 7, C→D −362347-3SABCD
노트 예제와 같은 그래프. 간선을 훑는 순서만 바꾼다.
풀이 보기

1바퀴 만에 모든 거리가 확정되고, 2바퀴째에 아무것도 줄지 않아 멈춘다. 거리는 A 5, B 2, C 9, D 6으로 같다.

벨먼-포드 단계 그림: 좋은 간선 순서라 첫 바퀴에 모든 거리가 확정된다62347-3S0A∞B∞C∞D∞시작62347-3S0A5B2C9D61바퀴째62347-3S0A5B2C9D62바퀴째62347-3S0A5B2C9D6확인 바퀴한 바퀴에 훑는 간선 순서: S→A, S→B, B→A, A→C, B→D, C→D(간선 6개 × 바퀴 수 = O(VE))이번 바퀴에 줄어든 점이번 바퀴에 완화한 간선
1 / 4

S 거리 0, 나머지는 ∞. 간선은 그림 아래 순서대로 훑는다.

출발점에서 가까운 간선부터 훑으면 한 바퀴 안에서 줄어든 값이 바로 다음 간선으로 이어진다.

한 바퀴 안에서 B가 2로 준 뒤 B→A가 그 값을 바로 쓰고, A가 5로 준 뒤 A→C가 쓰는 식이다. 간선을 위상 순서로 훑으면 늘 한 바퀴면 된다. V − 1바퀴는 가장 나쁜 순서에 대비한 상한이고, "줄어든 것이 없으면 멈춤"을 두면 좋은 순서에서는 그만큼 빨리 끝난다.

추적

아래 방향 그래프에서 S부터 벨먼-포드를 돌린다. 한 바퀴에 간선을 C→D, A→C, B→A, D→B, S→A, S→B, A→D 순서로 훑는다. 바퀴마다 줄어든 거리를 적고, 최종 거리와 음수 사이클 여부를 답하라.

방향 그래프: C→D 2, A→C 3, B→A −4, D→B 1, S→A 5, S→B 6, A→D 823-41568SABCD
음수 간선 B→A(−4)가 있는 방향 그래프.
풀이 보기

1바퀴 A 5, B 6, D 13 / 2바퀴 C 8, A 2, D 10 / 3바퀴 C 5 / 4바퀴 D 7. 최종 거리는 A 2, B 6, C 5, D 7이고, 확인 바퀴에서 줄지 않아 음수 사이클은 없다.

벨먼-포드 단계 그림: 네 바퀴에 걸쳐 거리가 줄고 확인 바퀴로 끝난다23-41568S0A∞B∞C∞D∞시작23-41568S0A5B6C∞D131바퀴째23-41568S0A2B6C8D102바퀴째23-41568S0A2B6C5D103바퀴째23-41568S0A2B6C5D74바퀴째23-41568S0A2B6C5D7확인 바퀴한 바퀴에 훑는 간선 순서: C→D, A→C, B→A, D→B, S→A, S→B, A→D(간선 7개 × 바퀴 수 = O(VE))이번 바퀴에 줄어든 점이번 바퀴에 완화한 간선
1 / 6

S 거리 0, 나머지는 ∞. 간선은 그림 아래 순서대로 훑는다.

점이 다섯이라 4바퀴까지 돈다. 이번에는 정말 4바퀴가 모두 필요했다.

최단 경로 S → B → A → C → D는 간선 4개다. 훑는 순서가 이 경로의 거꾸로라 바퀴마다 경로를 한 칸씩만 늘려, 간선 4개짜리 경로가 4바퀴째에야 완성됐다. D → B → A → C → D는 1 − 4 + 3 + 2 = 2로 양수인 사이클이라 문제가 없다.

응용

환율이 USD → EUR 0.9, EUR → JPY 160, JPY → USD 0.0072다. 한 바퀴 환전하면 돈이 불어나는가? 간선 가중치를 −ln(환율)로 바꾸면 이 질문이 벨먼-포드의 어떤 질문이 되는지 설명하라.

환전 그래프: USD에서 EUR로 0.9, EUR에서 JPY로 160, JPY에서 USD로 0.00720.91600.0072USDEURJPY
간선 위 숫자가 환율이다.
풀이 보기

불어난다. 0.9 × 160 × 0.0072 = 1.0368이라 한 바퀴에 약 3.7%가 남는다. 가중치를 −ln(환율)로 바꾸면 이 고리는 합이 -0.036인 음수 사이클이 된다.

가중치 −ln 환율: USD에서 EUR 0.105, EUR에서 JPY −5.075, JPY에서 USD 4.934, 합은 음수USDEURJPYUSD → EUR: −ln(0.9) = 0.105EUR → JPY: −ln(160) = -5.075JPY → USD: −ln(0.0072) = 4.934합: -0.036 < 0
같은 고리의 가중치를 −ln(환율)로 바꿨다(소수 셋째 자리까지, 아래 줄에 적음).

곱이 1보다 크다는 것은 로그를 씌운 합 ln r₁ + ln r₂ + ln r₃이 0보다 크다는 것이고, 부호를 바꾸면 −ln r의 합이 0보다 작다는 것이다. 곱셈을 덧셈으로, "크다"를 "작다"로 바꿔 차익 고리 찾기가 음수 사이클 찾기가 됐다. 벨먼-포드를 V − 1바퀴 돈 뒤 한 바퀴 더 돌았을 때 줄어드는 간선이 있으면 그런 고리가 있다. 실제로는 수수료와 환율 변동 때문에 이런 고리는 아주 잠깐만 생긴다.

플로이드-워셜

확인

플로이드-워셜의 세 겹 반복에서 k(경유 점)를 가장 안쪽으로 옮겨 "for i, for j, for k"로 한 번만 훑었다. 아래 그래프에서 2 → 1 거리는 무엇으로 나오는가? 올바른 답과 비교하라.

방향 그래프: 1→2 4, 2→3 3, 3→4 1, 4→1 2, 2→4 7431271234
점 넷, 간선 다섯의 방향 그래프.
풀이 보기

잘못된 순서는 2 → 1을 9로 내지만, 올바른 답은 6(2 → 3 → 4 → 1 = 3 + 1 + 2)다.

올바른 순서와 k가 안쪽인 순서의 거리 행렬. 2행 1열이 6과 9로 다르다올바름 1행0478올바름 2행6034올바름 3행3701올바름 4행2690k 안쪽 1행0478k 안쪽 2행9034k 안쪽 3행3701k 안쪽 4행2690
두 순서로 구한 거리 행렬. 행이 출발, 열이 도착(1~4)이고, 빨간 칸이 틀린 값이다.

k가 안쪽이면 dist[2][1]을 단 한 번, 그 순간의 표로 정한다. 그때 dist[2][4]는 아직 직행 7이라(2 → 3 → 4 = 4는 나중 칸 계산에서 생긴다) 7 + 2 = 9가 나오고 다시는 고쳐지지 않는다. k가 바깥이면 "1~k만 거치는 최단 거리"가 k마다 표 전체에서 완성된 뒤 다음 k로 넘어가므로, 쓰는 값이 늘 그 단계에서 이미 옳다.

추적

같은 그래프에 플로이드-워셜을 돌린다. 경유 점 k = 1, 2, 3, 4마다 줄어드는 칸을 적고 마지막 거리 행렬을 구하라.

처음 4×4 거리 행렬1에서04∞∞2에서∞0373에서∞∞014에서2∞∞0
처음 거리 행렬. 바로 잇는 간선이 없으면 ∞다.
풀이 보기

k=1: 4→2 = 6. k=2: 1→3 = 7, 1→4 = 11, 4→3 = 9. k=3: 1→4 = 8, 2→4 = 4. k=4: 2→1 = 6, 3→1 = 3, 3→2 = 7. 마지막 행렬은 1행 [0, 4, 7, 8], 2행 [6, 0, 3, 4], 3행 [3, 7, 0, 1], 4행 [2, 6, 9, 0]이다.

플로이드-워셜 단계 그림: 경유 점을 1부터 4까지 늘리며 거리 행렬을 고친다43127123411223344출발\도착04∞∞∞037∞∞012∞∞0경유 없음04∞∞∞037∞∞0126∞0경유 허용: 1 … 104711∞037∞∞012690경유 허용: 1 … 20478∞034∞∞012690경유 허용: 1 … 30478603437012690경유 허용: 1 … 4이번 경유 점 k의 행과 열k를 거쳐 줄어든 칸
1 / 5

처음 행렬: 바로 잇는 간선의 가중치, 간선이 없으면 ∞.

칠한 k행과 k열만 보면 그 단계에서 무엇이 줄지 알 수 있다.

1 → 4는 k=2에서 1 → 2 → 4 = 11이 됐다가 k=3에서 1 → 2 → 3 → 4 = 8로 한 번 더 준다. 칸 하나가 여러 번 고쳐질 수 있지만, k를 하나 늘릴 때마다 "쓸 수 있는 경유 점"이 하나씩 늘어 결국 모든 경로를 고려한다.

응용

다익스트라 문제의 그래프(점 S, A, B, C, D, T)에 병원 하나를 지으려 한다. "가장 먼 점까지의 거리"가 가장 작은 점을 고르면 어디인가? "모든 점까지 거리의 합"이 가장 작은 점은?

가중치 그래프: S-A 7, S-B 2, A-B 3, A-C 4, B-D 9, C-D 1, C-T 6, D-T 372349163SABCDT
다익스트라 문제와 같은 그래프.
풀이 보기

가장 먼 점까지 거리로는 A(8)가 가장 좋다. 거리 합으로는 A와 C가 25로 같다.

6×6 모든 쌍 거리 행렬과 행마다 최댓값. A행의 최댓값 8이 가장 작다A에서0345588B에서307821111C에서4701949D에서581010310S에서5291001313T에서8114313013
플로이드-워셜로 구한 모든 쌍 거리. 열 순서는 A, B, C, D, S, T이고 마지막 칸이 행의 최댓값(가장 먼 점까지 거리)이다.

모든 쌍 거리가 표 하나로 나오면 이런 질문은 행을 훑기만 하면 된다. 가장 먼 점까지의 거리를 줄이는 점은 그래프의 중심(center)이라 하고, 응급 시설처럼 "최악"이 중요할 때 쓴다. 합을 줄이는 점은 평균 이동이 중요할 때 쓴다. 두 기준의 답이 다를 수 있으니 문제에 맞는 기준을 먼저 정한다. 점이 6개라 O(V³) = 216번 계산이면 충분하다.

유니온 파인드

확인

유니온 파인드의 부모 배열이 아래와 같다. 집합은 몇 개인가? find(2)와 find(5)의 답은? 2와 0, 5와 7은 같은 집합인가? 경로 압축을 쓰면 find(5) 뒤 배열은 어떻게 바뀌는가?

부모 배열: 0, 0, 1, 3, 3, 4, 6, 6부모0001123334456667
원소 여덟 개의 부모 배열.
풀이 보기

집합은 3개(대표 0, 3, 6). find(2) = 0, find(5) = 3. 2와 0은 같은 집합, 5와 7은 다른 집합이다. find(5) 뒤에는 5의 부모가 4에서 3으로 바뀐다.

세 그루의 숲: 0 아래 1, 1 아래 2 / 3 아래 4, 4 아래 5 / 6 아래 701234567원소부모0010213343546676
부모 배열이 뜻하는 숲. 채운 원이 대표(자기 자신이 부모인 원소)다.

자기 자신을 가리키는 칸(0, 3, 6)이 뿌리다. find(5)는 5 → 4 → 3으로 올라가고, 경로 압축은 지나온 5를 뿌리 3에 바로 붙인다(4는 이미 3의 자식). 다음 find(5)는 한 걸음이면 끝난다.

추적

원소 0~5에 union(0, 1), union(2, 3), union(4, 5), union(1, 3), union(5, 2), find(5)를 차례로 한다. 랭크가 같으면 둘째 인자의 뿌리를 첫째 인자의 뿌리 밑에 붙이고 그 뿌리의 랭크를 1 올린다. 연산마다 부모 배열과 마지막 랭크를 적어라.

원소 여섯 개가 저마다 뿌리인 숲012345원소부모001122334455
처음에는 모두 혼자인 집합이다.
풀이 보기

부모 배열은 [0,0,2,3,4,5] → [0,0,2,2,4,5] → [0,0,2,2,4,4] → [0,0,0,2,4,4] → [0,0,0,2,0,4] → [0,0,0,2,0,0]이고, 마지막 랭크는 0번 2, 2번·4번 1, 나머지 0이다.

유니온 파인드 단계 그림: 여섯 원소를 합쳐 한 트리로 만들고 find(5)로 경로를 압축한다0123450123450랭크 1123450023450랭크 112랭크 13450022450랭크 112랭크 134랭크 150022440랭크 21234랭크 150002440랭크 2123450002040랭크 212345000200012345원소부모대표(뿌리)연산에 쓴 원소·대표부모가 바뀐 원소
1 / 7

원소 6개가 저마다 혼자인 집합이다. 부모가 자기 자신이면 대표(뿌리)다.

union(5, 2)에서는 랭크 1인 4번 뿌리가 랭크 2인 0번 뿌리 밑으로 들어가 높이가 늘지 않는다.

union(1, 3)은 1과 3이 아니라 두 뿌리 0과 2를 잇는다. 둘 다 랭크 1이라 2를 0 밑에 붙이고 0의 랭크가 2가 된다. union(5, 2)는 뿌리 4(랭크 1)와 0(랭크 2)이라 낮은 4가 0 밑으로 간다. 마지막 find(5)가 5 → 4 → 0을 따라 올라간 뒤 5를 0에 바로 붙인다. 랭크는 경로 압축 뒤에도 고치지 않는다. 그래서 랭크는 "높이"가 아니라 "높이의 상한"이다.

응용

원소 0~7에 union(0, 1), union(1, 2), …, union(6, 7)을 차례로 한다. 랭크 없이 "늘 첫째 뿌리를 둘째 뿌리 밑에 붙이는" 단순한 합치기와, 랭크 기준 합치기로 만든 트리의 높이를 비교하라. find(0)은 각각 몇 걸음인가?

단순한 합치기의 결과: 0부터 7까지 한 줄로 이어진 트리, 뿌리는 7단순한 합치기76543210원소부모0112233445566777
단순한 합치기로 만든 모양.
풀이 보기

단순한 합치기는 높이 7인 한 줄이 되어 find(0)이 7걸음이다. 랭크 기준 합치기는 모두 0 밑에 붙어 높이 1, find(0)은 0걸음(0이 뿌리), 다른 원소도 1걸음이다.

랭크 기준 합치기의 결과: 0 아래 나머지 일곱 원소가 모두 바로 붙은 납작한 트리랭크 기준 합치기01234567원소부모0010203040506070
랭크 기준 합치기로 만든 모양. 0이 처음에 랭크 1이 된 뒤로는 늘 랭크 0인 원소가 0 밑으로 들어온다.

단순한 합치기는 큰 트리를 작은 트리 밑에 붙이는 일을 막지 못해, 이런 순서에서 원소 n개가 한 줄(높이 n − 1)이 된다. 그러면 find 하나가 O(n)이다. 랭크 기준 합치기는 높은 쪽을 늘 위에 두므로 높이가 O(log n)을 넘지 않고, 경로 압축까지 더하면 사실상 상수가 된다.

크루스칼·프림

확인

아래 그래프에서 점 C 하나와 나머지 다섯 점으로 나누는 자르기를 생각하자. 이 자르기를 건너는 간선 가운데 최소 신장 트리(MST)에 반드시 들어가는 것은? 가장 무거운 간선 E-F(9)는 MST에 들어갈 수 있는가?

그래프: A-B 6, B-C 8, A-D 3, B-E 5, C-F 7, D-E 2, E-F 9, A-E 4, B-F 1. C를 칠했다683572941ABCDEF
가중치가 모두 다른 여섯 점 그래프. 칠한 C가 자르기의 한쪽이다.
풀이 보기

건너는 간선은 B-C(8)와 C-F(7)이고, 가벼운 C-F가 반드시 들어간다. E-F(9)는 들어갈 수 없다.

C-F를 굵게, E-F를 빨간 점선으로 표시한 그래프683572941ABCDEF
자르기를 건너는 간선 가운데 가장 가벼운 C-F(굵게)와, 사이클 B-E-F에서 가장 무거운 E-F(빨간 점선).

자르기 성질: 어떤 자르기든 건너는 가장 가벼운 간선은 MST에 들어간다. C-F는 무게 7로 그래프 전체에서 무거운 편이지만, C에 닿는 길이 둘뿐이라 들어간다. 반대로 사이클 B-E-F(5, 9, 1)에서 가장 무거운 E-F는 빼도 세 점이 이어져 있으니 MST에 들어가지 않는다(사이클 성질).

추적

같은 그래프에 크루스칼을 돌린다. 간선을 보는 순서대로 고르는지 버리는지 적고, MST의 간선과 총 무게를 구하라.

그래프: A-B 6, B-C 8, A-D 3, B-E 5, C-F 7, D-E 2, E-F 9, A-E 4, B-F 1683572941ABCDEF
앞 문제와 같은 그래프.
풀이 보기

B-F(1) 고름, D-E(2) 고름, A-D(3) 고름, A-E(4) 버림, B-E(5) 고름, A-B(6) 버림, C-F(7) 고름. MST는 B-F, D-E, A-D, B-E, C-F이고 총 무게 18이다.

크루스칼 단계 그림: 간선 일곱 개를 보며 다섯 개를 골라 총 무게 18인 MST를 만든다683572941ABCDEFB-F 1D-E 2A-D 3A-E 4B-E 5A-B 6C-F 7B-C 8E-F 9총 무게 0683572941ABCDEFB-F 1D-E 2A-D 3A-E 4B-E 5A-B 6C-F 7B-C 8E-F 9총 무게 1683572941ABCDEFB-F 1D-E 2A-D 3A-E 4B-E 5A-B 6C-F 7B-C 8E-F 9총 무게 3683572941ABCDEFB-F 1D-E 2A-D 3A-E 4B-E 5A-B 6C-F 7B-C 8E-F 9총 무게 6683572941ABCDEFB-F 1D-E 2A-D 3A-E 4B-E 5A-B 6C-F 7B-C 8E-F 9총 무게 6683572941ABCDEFB-F 1D-E 2A-D 3A-E 4B-E 5A-B 6C-F 7B-C 8E-F 9총 무게 11683572941ABCDEFB-F 1D-E 2A-D 3A-E 4B-E 5A-B 6C-F 7B-C 8E-F 9총 무게 11683572941ABCDEFB-F 1D-E 2A-D 3A-E 4B-E 5A-B 6C-F 7B-C 8E-F 9총 무게 18683572941ABCDEFB-F 1D-E 2A-D 3A-E 4B-E 5A-B 6C-F 7B-C 8E-F 9총 무게 18무게 순 간선고른 간선사이클이 생겨 버린 간선
1 / 9

간선을 가벼운 순으로 줄 세운다. 점마다 혼자인 집합에서 시작한다.

간선 5개(점 수 − 1)를 고르면 멈추므로 B-C(8)와 E-F(9)는 보지도 않는다.

A-E(4)를 볼 때 A, D, E는 A-D와 D-E로 이미 이어져 있어 사이클이 생긴다. A-B(6)도 B-E가 들어온 뒤라 A와 B가 이미 한 덩어리다. 이 "이미 이어졌나"를 유니온 파인드의 find 두 번으로 답한다.

추적

같은 그래프에 C에서 시작하는 프림을 돌린다. 점이 트리에 들어오는 순서와 고르는 간선, 총 무게를 구하라.

같은 그래프에서 시작점 C를 칠했다683572941ABCDEF
시작점 C를 칠했다.
풀이 보기

C → F(C-F 7) → B(B-F 1) → E(B-E 5) → D(D-E 2) → A(A-D 3) 순이고 총 무게는 18이다.

프림 단계 그림: C에서 시작해 F, B, E, D, A 순으로 트리를 키운다683572941ABCDEF총 무게 0683572941ABCDEF총 무게 7683572941ABCDEF총 무게 8683572941ABCDEF총 무게 13683572941ABCDEF총 무게 15683572941ABCDEF총 무게 18트리에 든 점고른 간선방금 고른 간선후보(안과 밖을 잇는 간선)
1 / 6

C에서 시작한다. 트리와 바깥을 잇는 간선이 후보다.

C에서 시작한 프림. 첫 간선은 무게 7이지만 C에서 나가는 간선 중에는 가장 가볍다.

프림은 첫 단계에 C에 닿은 간선(8과 7)밖에 볼 수 없어 무거운 7부터 고른다. 크루스칼과 고르는 순서는 완전히 다르지만, 가중치가 모두 달라 MST가 하나뿐이므로 같은 다섯 간선, 같은 무게 18에 닿는다.

응용

여섯 점을 비슷한 것끼리 세 무리로 나누려 한다. 같은 그래프의 MST에서 간선을 어떻게 지우면 되는가? 결과 무리는?

MST 간선 B-F 1, D-E 2, A-D 3, B-E 5, C-F 7을 굵게 칠한 그래프683572941ABCDEF
크루스칼로 구한 MST(굵은 선).
풀이 보기

MST에서 가장 무거운 간선 두 개 C-F(7)와 B-E(5)를 지운다. 무리는 {C}, {B, F}, {A, D, E}다.

MST에서 B-E와 C-F를 지워 C, B-F, A-D-E 세 무리로 나뉜 그래프683572941ABCDEF
MST에서 무거운 두 간선(빨간 점선)을 지우면 세 덩어리가 남는다.

MST는 n개 점을 n − 1개 간선으로 잇는다. 간선 k − 1개를 지우면 정확히 k개 덩어리가 된다. 가장 무거운 것부터 지우면 무리 사이의 가장 가까운 거리가 최대가 되는 나눔이 나온다(단일 연결 군집화). 크루스칼을 간선이 n − k개가 될 때 멈춰도 같은 결과다.