코딩테스트 공부

최단 경로

원준킹 2021. 1. 6. 17:30

최단 경로 알고리즘

- 가장 짧은 경로를 찾는 알고리즘

- 길 찾기 문제

- 다양한 사례 ex

    - 한 지점에서 다른 특정 지점까지의 최단 경로를 구해야 하는 경우

    - 모든 지점에서 다른 모든 지점까지의 최단 경로를 모두 구해야 하는 경우

- 그래프를 이용 / 노드 : 지점, 간선 : 경로 

 

다익스트라

- 가장 비용이 적은 노드를 선택하는 과정 반복 (= 그리디 알고리즘)

- 작동 과정

    1. 출발 노드 설정

    2. 최단 거리 테이블 초기화

    3. 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드를 선택

    4. 해당 노드를 거쳐 다른 노드로 가는 비용을 계산하여 최단거리 테이블 갱신

    5. 3, 4 반복

 

   

1. 출발 노드 설정

노드 번호 1 2 3 4 5 6
거리 0 INF INF INF INF INF

- 1에서 시작

 

 

2. 최단 거리 테이블 초기화

노드 번호 1 2 3 4 5 6
거리 0 2 5 1 INF INF

- 1에서 갈 수 있는 각 노드까지의 거리 입력

 

 

3. 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드를 선택

노드 번호 1(방문완료) 2 3 4(선택) 5 6
거리 0 2 5 1 INF INF

- 노드 4 선택

- 노드 4에서는 노드 3, 5 방문 가능

 

 

4. 해당 노드를 거쳐 다른 노드로 가는 비용을 계산하여 최단거리 테이블 갱신

노드 번호 1(방문완료) 2 3 4(선택) 5 6
거리 0 2 5 -> 4 1 INF -> 2 INF

- 노드 4 까지의 최단거리는 현재 1이므로 노드 4를 거쳐서 노드 3, 노드 5까지의 최단 거리는 4(1+3), 2(1+1)

- 현재 리스트에 있는 노드 3, 노드 5까지의 최단거리보다 짧으니까 둘다 갱신 

- 위 작업을 선택할 노드가 없을 때 까지 진행

 

3. 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드를 선택

노드 번호 1(방문완료) 2(선택) 3 4(방문완료) 5 6
거리 0 2 4 1 2 INF

- 노드 2 선택 (노드 5도 거리가 2지만 일반적으로 같은 거리일 때는 번호가 작은거 고르자)

- 노드 2에서는 노드 3, 4 방문 가능

 

 

4. 해당 노드를 거쳐 다른 노드로 가는 비용을 계산하여 최단거리 테이블 갱신

노드 번호 1(방문완료) 2(선택) 3 4(방문완료) 5 6
거리 0 2 4 1 2 INF

- 노드 2 까지의 최단거리는 현재 2이므로 노드 2를 거쳐서 노드 3, 노드 4까지의 최단 거리는 5(2+3), 4(2+2)

- 현재 리스트에 있는 둘다 리스트에 있는 최단거리보다 크니까 갱신 둘 다 안함

 

3. 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드를 선택

노드 번호 1(방문완료) 2(방문완료) 3 4(방문완료) 5(선택) 6
거리 0 2 4 1 2 INF

- 노드 5 선택

- 노드 5에서는 노드 3, 6 방문 가능

 

 

4. 해당 노드를 거쳐 다른 노드로 가는 비용을 계산하여 최단거리 테이블 갱신

노드 번호 1(방문완료) 2(방문완료) 3 4(방문완료) 5(선택) 6
거리 0 2 4 -> 3 1 2 INF -> 4

- 노드 5 까지의 최단거리는 현재 2이므로 노드 5를 거쳐서 노드 3, 노드 6까지의 최단 거리는 3(2+1), 4(2+2)

- 현재 리스트에 있는 노드 3, 노드 6까지의 최단거리보다 짧으니 둘 다 갱신

 

 

3. 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드를 선택

노드 번호 1(방문완료) 2(방문완료) 3(선택) 4(방문완료) 5(방문완료) 6
거리 0 2 3 1 2 4

- 노드 3 선택

- 노드 3에서는 노드 2, 6 방문 가능

 

 

4. 해당 노드를 거쳐 다른 노드로 가는 비용을 계산하여 최단거리 테이블 갱신

노드 번호 1(방문완료) 2(방문완료) 3(선택) 4(방문완료) 5(방문완료) 6
거리 0 2 3 1 2 4

- 노드 3 까지의 최단거리는 현재 3이므로 노드 3를 거쳐서 노드 2, 노드 6까지의 최단 거리는 6(3+3), 8(3+5)

- 현재 리스트에 있는 노드 2, 노드 6까지의 최단거리보다 크니 둘 다 갱신 안함

 

 

3. 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드를 선택

노드 번호 1(방문완료) 2(방문완료) 3(방문완료) 4(방문완료) 5(방문완료) 6(선택)
거리 0 2 3 1 2 4

- 노드 6 선택

- 노드 6에서는 방문 가능 노드 없음

 

 

4. 해당 노드를 거쳐 다른 노드로 가는 비용을 계산하여 최단거리 테이블 갱신

노드 번호 1(방문완료) 2(방문완료) 3(방문완료) 4(방문완료) 5(방문완료) 6(선택)
거리 0 2 3 1 2 4

 

 

최종

노드 번호 1(방문완료) 2(방문완료) 3(방문완료) 4(방문완료) 5(방문완료) 6(방문완료)
거리 0 2 3 1 2 4

- 한 단계당 하나의 최단 거리 확정

- 그 말은 마지막 단계에선 사실상 변동될 일이 없다는 뜻

 

 

 

간단한 다익스트라 알고리즘

- 작동원리 그대로 구현

- O(V^2) (V : 노드 개수) 

- 전체 노드 개수가 5000개 이하면 문제없으나 10000개가 넘어가면 개선된 다익스트라 알고리즘

- 최단 거리가 가장 짧은 노드를 찾기 위해서 매번 최단 거리 테이블을 선형적으로 탐색

 

#다익스트라
MAP1 = [
    [],
    [(2,2),(3,5),(4,1)],
    [(3,3),(4,2)],
    [(2,3),(6,5)],
    [(3,3),(5,1)],
    [(3,1),(6,2)],
    []
]

INF = int(1e9)


def ds(MAP, node_num,start):
    dis = [INF] * (node_num + 1)
    dis_visited = [True] + [False] * node_num

    dis[start] = 0


    #선택할 수 있는 노드가 있는 동안
        #dis 원소 중 가장 작은거 선택
        #map[그 원소의 인덱스]
            #튜플 두번째원소 거리 더하기 아까 가장 작은거
            #그게 dis[첫번째원소] 보다 작으면 갱신
        #노드 선택
    while False in dis_visited:
        MIN = [INF,INF] #값, 인덱스

        for i in range(1,node_num+1):
            if dis_visited[i] == False:
                if MIN[0] > dis[i]:
                    MIN[0] = dis[i]
                    MIN[1] = i

        for x,y in MAP[MIN[1]]:
            if dis[x] > y + MIN[0]:
                dis[x] = y + MIN[0]

        dis_visited[MIN[1]] = True
        #print("debug",dis_visited)
        #print(dis)
    return dis

print(ds(MAP1,6,1))

 

개선된 다익스트라 알고리즘

- WORST CASE 에도 O(ElogV) (E : 간선 개수, V : 노드 개수)

- 최단 거리가 가장 짧은 노드를 찾는 과정을 개선 

 

 

직접 짰는데 결과는 맞지만 큐가 이상하게 삽입이 더 많이 되는 문제가 있는 버전

#개선된 다익스트라
import heapq

n, m = map(int,input().split()) #노드, 간선
start = int(input())

graph = [[] for x in range(n+1)]

for _ in range(m):
    a, b, c = map(int,input().split())
    graph[a].append([b,c])



def ds(GRAPH, START):
    INF = int(1e9)
    q = []

    NODE_NUM = len(GRAPH)-1
    dist = [INF] * (NODE_NUM+1)

    q = [(0, START)]


    #큐에 있을 동안
        #큐에서 꺼낸게 현재 거리정보와 비교했을 때 더 짧으면
            #갱신
            #현재거리 + 다음거리가 다음 노드의 최단거리보다 짧으면
                #현재 거리 + 다음 노드 거리 들을 큐에 삽입
        #아니면
            #다음 큐 꺼냄

    while q:
        now_dis, now = heapq.heappop(q)
        print('poped', q)
        if dist[now] > now_dis:
            dist[now] = now_dis

            for next_, next_dis in GRAPH[now]:
                if next_dis + dist[now] < dist[next_]:
                    heapq.heappush(q,(next_dis + dist[now],next_))
                    print('pushed', q)


        else:
            continue
    return dist

print(ds(graph,1))

 

책 정답 버전

import heapq
import sys
input = sys.stdin.readline
INF =int(1e9)

n, m = map(int,input().split())
start = int(input())
graph = [[] for i in range(n+1)]
distance = [INF] * (n+1)

for _ in range(m):
    a ,b, c= map(int,input().split())
    graph[a].append((b,c))

def ds(start):
    q = []
    heapq.heappush(q, (0, start))
    distance[start]=0
    while q:
        dist,now=heapq.heappop(q)
        print('pop',q)
        if distance[now]<dist:
            continue
        for i in graph[now]:
            cost=dist+i[1]
            if cost<distance[i[0]]:
                distance[i[0]]=cost
                heapq.heappush(q,(cost,i[0]))
                print('push',q)

print(ds(start))

 

직접 짠거 수정 버전

#개선된 다익스트라
import heapq

n, m = map(int,input().split()) #노드, 간선
start = int(input())

graph = [[] for x in range(n+1)]

for _ in range(m):
    a, b, c = map(int,input().split())
    graph[a].append([b,c])



def ds(GRAPH, START):
    INF = int(1e9)
    q = []

    NODE_NUM = len(GRAPH)-1
    dist = [INF] * (NODE_NUM+1)

    q = [(0, START)]
    dist[START] = 0



    #큐에 있을 동안
        #큐에서 꺼낸게 현재 거리정보와 비교했을 때 더 짧으면
            
            #현재거리 + 다음거리가 다음 노드의 최단거리보다 '같거나' 짧으면
                #현재 거리 + 다음 노드 거리 들을 큐에 삽입
                #갱신
        #아니면
            #다음 큐 꺼냄

    while q:
        now_dis, now = heapq.heappop(q)
        print('poped', q)
        if dist[now] >= now_dis:
            for next_, next_dis in GRAPH[now]:
                if next_dis + dist[now] < dist[next_]:
                    heapq.heappush(q,(next_dis + dist[now],next_))
                    dist[next_] = next_dis + dist[now]
                    print('pushed', q)


        else:
            continue
    return dist

print(ds(graph,1))

 

 

플로이드 워셜 

- 모든 지점에서 다른 모든 지점까지의 최단 경로를 모두 구해야 하는 경우

- N 번의 단계마다 O(N^2) 번의 연산 -> O(N^3)

- 다익스트라 최단거리 정보 == 1차원 리스트, 플로이드 워셜 최단거리 리스트 == 2차원 리스트

- 다이나믹 프로그래밍 - 노드의 개수 만큼의 단계를 반복하며 점화식에 맞게 2차원 리스트를 갱신하기 때문

- 점화식 : D<a,b> = min(D<a,b>, D<a,k> + D<k,b>)

 

n, m = map(int,input().split())
# 1 2 3 4
#1
#2
#3
#4
INF = int(1e9)
graph = [[INF]*(n+1) for x in range(n+1)]

for i in range(1,n+1):
    graph[i][i] = 0

for i in range(m):
    a, b, c = map(int, input().split())
    graph[a][b] = c
    
    
#1~n 번째 노드를 거쳐서 목적지에 가는 방법
    #j마다
        #k마다
            #graph[j][k]에서 j나 k중 하나가 거치는 노드랑 같으면
                #컨티뉴
            #현재 위치와 노드를 거쳤을 때 둘중 거리가 짧은 것 선택, 갱신


for i in range(1,n+1):
    for j in range(1,n+1):
        for k in range(1,n+1):
            if j == i or k == i:
                continue

            graph[j][k] = min(graph[j][k], graph[j][i] + graph[i][k])
print(graph)




 

EZ

 

문제 1. 미래 도시 (난이도 2, 풀이 시간 17분/40분, 정답)

#미래도시 2:54 3:11

#도시 간 간격 일정 1
#1 -> K -> X
#1->K의 최소이동값 + K->X의 최소이동값




N, M = map(int,input().split()) #회사개수 경로개수

INF = int(1e9)
graph = [[INF]*(N+1) for _ in range(N+1)]
for i in range(1,N+1):
    graph[i][i] = 0

for _ in range(M):
    a,b = map(int, input().split())
    graph[a][b] = 1
    graph[b][a] = 1

X, K = map(int,input().split())

for i in range(1,N+1):
    for j in range(1,N+1):
        for k in range(1,N+1):
            if j == i or k == i:
                continue

            graph[j][k] = min(graph[j][k], graph[j][i] + graph[i][k])

res = graph[1][K]+graph[K][X]
if res >= INF:
    print(-1)
else:
    print(res)

 

- 플로이드 워셜로 전체 노드에서 전체 노드까지 최단거리 구해놓고 1->K + K->X 하면 간단히 풀리는 문제

- N의 범위가 100 이하로 매우 한정적이어서 O(N^3)인 플로이드 워셜 알고리즘을 써도 빨리 풀 수 있었다.

 

 

문제 2. 전보 (난이도 3, 58분/60분, 틀림)

 

내 오답

import heapq
#전보 3:22 4:20
#X->Y일방향 통로, 보낼땐 일정시간소요
#한 도시에서 출발하여 메세지를 최대한 많이 퍼져나가게, 도시들이 모두 메세지를 받는데 걸리는 시간(제일 경로가 긴 곳 도달 시간)

#최대한 긴 한붓그리기
#정해진 시작지점 하나부터 최장경로 = 다익스트라, 도시간 거리가 1이라고 가정시에 -1로 최단경로
#
N, M, C = map(int,input().split()) #도시개수, 통로개수, 스타트 도시

INF = int(1e9)
graph = [[] for _ in range(N+1)]


for _ in range(M):
    a,b,c = map(int, input().split())
    graph[a].append((b,c)) #목적지, 거리


def ds(GRAPH, START):
    q = []
    dist = [[INF,0] for _ in range(N+1)]

    heapq.heappush(q,(0,START))

    while q:
        now_dist, now = heapq.heappop(q)
        if dist[now][0] >= now_dist:
            for next_, next_dist in GRAPH[now]:
                if -1 + now_dist < dist[next_][0]:
                    dist[next_][0] = -1 + now_dist
                    dist[next_][1] = next_dist + dist[now][1]
                    heapq.heappush(q,(-1,next_))
        else:
            continue
    return dist


city = 0
max_time = 0
for i in ds(graph,C):
    if i[0] < 0 and i[0] != INF:
        city += 1
        if max_time < i[1]:
            max_time = i[1]

print(city,max_time)




 

import heapq
#전보 3:22
#X->Y일방향 통로, 보낼땐 일정시간소요
#한 도시에서 출발하여 메세지를 최대한 많이 퍼져나가게, 도시들이 모두 메세지를 받는데 걸리는 시간(제일 경로가 긴 곳 도달 시간)


N, M, C = map(int,input().split()) #도시개수, 통로개수, 스타트 도시

INF = int(1e9)
graph = [[] for _ in range(N+1)]


for _ in range(M):
    a,b,c = map(int, input().split())
    graph[a].append((b,c)) #목적지, 거리


def ds(GRAPH, START):
    q = []
    dist = [INF]*(N+1)
    dist[START] = 0

    heapq.heappush(q,(0,START))

    while q:
        now_dist, now = heapq.heappop(q)
        if dist[now] >= now_dist:
            for next_, next_dist in GRAPH[now]:
                if next_dist + now_dist < dist[next_]:
                    dist[next_] = next_dist + now_dist
                    heapq.heappush(q,(next_dist,next_))
        else:
            continue
    return dist

dist = ds(graph,1)
#방문 가능한 노드의 개수 = 메세지를 받는 총 도시의 개수 = 다익스트라 거리정보 개수 - 출발지,INF
res = 0
max_dist = 0
for i in dist:
    if i != INF and i != 0:
        res += 1
        max_dist = max(max_dist, i)
print(res, max_dist)



 

 

- 방문 가능한 노드 개수 = 메세지 전송 가능한 총 도시 개수

- 다익스트라 거리 정보에 있는 값 중 INF 제외 제일 큰게 총 시간

 

 

'코딩테스트 공부' 카테고리의 다른 글

구현 문제 풀이  (0) 2021.01.09
그리디 문제 풀이  (0) 2021.01.06
다이나믹 프로그래밍  (0) 2020.12.31
이진 탐색  (0) 2020.12.30
정렬  (0) 2020.12.29