반응형
https://github.com/ndb796/python-for-coding-test
그래프 알고리즘
- 크루스칼
- 그리디 알고리즘
- 위상 정렬
- 큐 or 스택
- 그래프
- 노드와 간선 정보를 가지고 있는 자료구조
연결
- 트리
- 최소 힙
- 부모에서 자식으로 내려오는 계층적인 모델
- 부모가 항상 자식보다 작은 자료구조
- 최소 힙
- 그래프의 구현 방법
- 인접 행렬 : 2차원 배열을 사용하는 방식 V^2 메모리 공간 시간 복잡도 1
- 인접 리스트 : 리스트를 활용하는 방식 E 메모리 공간 시간 복잡도 V
서로소 집합(Disjoint Sets)
- 공통 원소가 없는 두 집합
- union-find 자료 구조
- 집합을 하나로
- 특정한 원소가 속한 집합 찾기
- 알고리즘
- union 연산으로 A,B 노드 확인
- A,B의 루트 A',B' 찾기
- A'를 B'의 부모 노드로 설정
- 모든 합집합 연산 처리까지 1 반복
- union 연산으로 A,B 노드 확인
# 특정 원소가 속한 집합을 찾기
def find_parent(parent, x):
# 루트 노드가 아니라면, 루트 노드를 찾을 때까지 재귀적으로 호출
if parent[x] == x:
parent[x] = find_parent(parent, parent[x])
return parent[x]
# 두 원소가 속한 집합을 합치기
def union_parent(parent,rank, a, b):
a = find_parent(parent, a)
b = find_parent(parent, b)
if a == b :
return # 이미 같은 트리 - 사이클
if rank[a] < rank[b]:
parent[a] = b # 높이가 낮은 트리를 높은 트리에 넣음
else:
parent[b] = a
if rank[a] == rank[b]:
rank[a]+=1
# O(V + Mlog2V) (M은 쿼리 == find 연산 후)
- 무방향 그래프 사이클 판별
- 방향 그래프 사이클 여부는 DFS로 판별
신장 트리 Spanning Tree
- 하나의 그래프가 있을 때 모든 를 포함하며 사이클이 존재하지 않는 부분 그래프크루스칼 알고리즘
- 간선 데이터를 오름차순 정렬
- 사이클이 발생하지 않으면 하나씩 넣음
- n-1개만큼
- 노드 7 간선 6개
- 사이클이 발생하면 안넣음
# 특정 원소가 속한 집합을 찾기
def find_parent(parent, x):
# 루트 노드가 아니라면, 루트 노드를 찾을 때까지 재귀적으로 호출
if parent[x] == x:
parent[x] = find_parent(parent, parent[x])
return parent[x]
# 두 원소가 속한 집합을 합치기
def union_parent(parent,rank, a, b):
a = find_parent(parent, a)
b = find_parent(parent, b)
if a == b :
return # 이미 같은 트리 - 사이클
if rank[a] < rank[b]:
parent[a] = b # 높이가 낮은 트리를 높은 트리에 넣음
else:
parent[b] = a
if rank[a] == rank[b]:
rank[a]+=1
# O(V + Mlog2V) (M은 쿼리 == find 연산 후)
# 노드의 개수와 간선(Union 연산)의 개수 입력 받기
v, e = map(int, input().split())
parent = [0] * (v + 1) # 부모 테이블 초기화하기
# 모든 간선을 담을 리스트와, 최종 비용을 담을 변수
edges = []
result = 0
# 부모 테이블상에서, 부모를 자기 자신으로 초기화
for i in range(1, v + 1):
parent[i] = i
# 모든 간선에 대한 정보를 입력 받기
for _ in range(e):
a, b, cost = map(int, input().split())
# 비용순으로 정렬하기 위해서 튜플의 첫 번째 원소를 비용으로 설정
edges.append((cost, a, b))
# 간선을 비용순으로 정렬
edges.sort()
# 간선을 하나씩 확인하며
for edge in edges:
cost, a, b = edge
# 사이클이 발생하지 않는 경우에만 집합에 포함
if find_parent(parent, a) != find_parent(parent, b):
union_parent(parent, a, b)
result += cost
print(result)
위상 정렬
- 방향 그래프의 모든 노드를 방향성에 거스르지 않도록 순서대로 나열하는 것
-
진입차수가 0인 노드를 큐에 넣음
-
repeat
- 큐에서 원소를 꺼내 해당 노드에서 출발하는 간선을 그래프에서 제거한다
- 새롭게 0이 된 간선을 큐에 넣는다.
O(V+E)
from collections import deque
# 노드의 개수와 간선의 개수를 입력 받기
v, e = map(int, input().split())
# 모든 노드에 대한 진입차수는 0으로 초기화
indegree = [0] * (v + 1)
# 각 노드에 연결된 간선 정보를 담기 위한 연결 리스트 초기화
graph = [[] for i in range(v + 1)]
# 방향 그래프의 모든 간선 정보를 입력 받기
for _ in range(e):
a, b = map(int, input().split())
graph[a].append(b) # 정점 A에서 B로 이동 가능
# 진입 차수를 1 증가
indegree[b] += 1
# 위상 정렬 함수
def topology_sort():
result = [] # 알고리즘 수행 결과를 담을 리스트
q = deque() # 큐 기능을 위한 deque 라이브러리 사용
# 처음 시작할 때는 진입차수가 0인 노드를 큐에 삽입
for i in range(1, v + 1):
if indegree[i] == 0:
q.append(i)
# 큐가 빌 때까지 반복
while q:
# 큐에서 원소 꺼내기
now = q.popleft()
result.append(now)
# 해당 원소와 연결된 노드들의 진입차수에서 1 빼기
for i in graph[now]:
indegree[i] -= 1
# 새롭게 진입차수가 0이 되는 노드를 큐에 삽입
if indegree[i] == 0:
q.append(i)
# 위상 정렬을 수행한 결과 출력
for i in result:
print(i, end=' ')
topology_sort()
- 2개의 신장 트리 만들기
- 최소 신장 트리를 만든 뒤 가장 비용이 큰 간선을 제거한다.
# 특정 원소가 속한 집합을 찾기
def find_parent(parent, x):
# 루트 노드가 아니라면, 루트 노드를 찾을 때까지 재귀적으로 호출
if parent[x] != x:
parent[x] = find_parent(parent, parent[x])
return parent[x]
# 두 원소가 속한 집합을 합치기
def union_parent(parent, a, b):
a = find_parent(parent, a)
b = find_parent(parent, b)
if a < b:
parent[b] = a
else:
parent[a] = b
# 노드의 개수와 간선(Union 연산)의 개수 입력받기
v, e = map(int, input().split())
parent = [0] * (v + 1) # 부모 테이블 초기화
# 모든 간선을 담을 리스트와, 최종 비용을 담을 변수
edges = []
result = 0
# 부모 테이블상에서, 부모를 자기 자신으로 초기화
for i in range(1, v + 1):
parent[i] = i
# 모든 간선에 대한 정보를 입력받기
for _ in range(e):
a, b, cost = map(int, input().split())
# 비용순으로 정렬하기 위해서 튜플의 첫 번째 원소를 비용으로 설정
edges.append((cost, a, b))
# 간선을 비용순으로 정렬
edges.sort()
last = 0 # 최소 신장 트리에 포함되는 간선 중에서 가장 비용이 큰 간선
# 간선을 하나씩 확인하며
for edge in edges:
cost, a, b = edge
# 사이클이 발생하지 않는 경우에만 집합에 포함
if find_parent(parent, a) != find_parent(parent, b):
union_parent(parent, a, b)
result += cost
last = cost
print(result - last)
- 리스트 복사는 deepcopy()를 쓰자
# 동시에 여러 강의 수강 가능
from collections import deque
import copy
# 노드의 개수 입력받기
v = int(input())
# 모든 노드에 대한 진입차수는 0으로 초기화
indegree = [0] * (v + 1)
# 각 노드에 연결된 간선 정보를 담기 위한 연결 리스트(그래프) 초기화
graph = [[] for i in range(v + 1)]
# 각 강의 시간을 0으로 초기화
time = [0] * (v + 1)
# 방향 그래프의 모든 간선 정보를 입력받기
for i in range(1, v + 1):
data = list(map(int, input().split()))
time[i] = data[0] # 첫 번째 수는 시간 정보를 담고 있음
for x in data[1:-1]:
indegree[i] += 1
graph[x].append(i)
# 위상 정렬 함수
def topology_sort():
result = copy.deepcopy(time) # 알고리즘 수행 결과를 담을 리스트
q = deque() # 큐 기능을 위한 deque 라이브러리 사용
# 처음 시작할 때는 진입차수가 0인 노드를 큐에 삽입
for i in range(1, v + 1):
if indegree[i] == 0:
q.append(i)
# 큐가 빌 때까지 반복
while q:
# 큐에서 원소 꺼내기
now = q.popleft()
# 해당 원소와 연결된 노드들의 진입차수에서 1 빼기
for i in graph[now]:
# 동시에 들을 수 있음
result[i] = max(result[i], result[now] + time[i])
indegree[i] -= 1
# 새롭게 진입차수가 0이 되는 노드를 큐에 삽입
if indegree[i] == 0:
q.append(i)
# 위상 정렬을 수행한 결과 출력
for i in range(1, v + 1):
print(result[i])
topology_sort()
1. 여행 계획
- 여행 계회에 해당하는 노드가 같은 집팝에 속하기만 하면 된다.
# 특정 원소가 속한 집합을 찾기
def find_parent(parent, x):
# 루트 노드가 아니라면, 루트 노드를 찾을 때까지 재귀적으로 호출
if parent[x] == x:
parent[x] = find_parent(parent, parent[x])
return parent[x]
# 두 원소가 속한 집합을 합치기
def union_parent(parent,rank, a, b):
a = find_parent(parent, a)
b = find_parent(parent, b)
if a == b :
return # 이미 같은 트리 - 사이클
if rank[a] < rank[b]:
parent[a] = b # 높이가 낮은 트리를 높은 트리에 넣음
else:
parent[b] = a
if rank[a] == rank[b]:
rank[a]+=1
# O(V + Mlog2V) (M은 쿼리 == find 연산 후)
# 여행지의 개수와 여행 계획에 속한 여행지의 개수 입력받기
n, m = map(int, input().split())
parent = [0] * (n + 1) # 부모 테이블 초기화
rank = [0] * (n + 1) # 높이 테이블 초기화
# 부모 테이블상에서, 부모를 자기 자신으로 초기화
for i in range(1, n + 1):
parent[i] = i
# Union 연산을 각각 수행
for i in range(n):
data = list(map(int, input().split()))
for j in range(n):
if data[j] == 1: # 연결된 경우 합집합(Union) 연산 수행
union_parent(parent, i + 1, j + 1)
# 여행 계획 입력받기
plan = list(map(int, input().split()))
result = True
# 여행 계획에 속하는 모든 노드의 루트가 동일한지 확인
for i in range(m - 1):
if find_parent(parent, plan[i]) != find_parent(parent, plan[i + 1]):
result = False
# 여행 계획에 속하는 모든 노드가 서로 연결되어 있는지(루트가 동일한지) 확인
if result:
print("YES")
else:
print("NO")
2.탑승구
- 해당 루트에 비행기가 착륙하면 옆에 있는 노드와 합집합 해줌
- 루트가 0이면 도킹 못함
# 특정 원소가 속한 집합을 찾기
def find_parent(parent, x):
# 루트 노드가 아니라면, 루트 노드를 찾을 때까지 재귀적으로 호출
if parent[x] != x:
parent[x] = find_parent(parent, parent[x])
return parent[x]
# 두 원소가 속한 집합을 합치기
def union_parent(parent, a, b):
a = find_parent(parent, a)
b = find_parent(parent, b)
if a < b:
parent[b] = a
else:
parent[a] = b
# 탑승구의 개수 입력받기
g = int(input())
# 비행기의 개수 입력받기
p = int(input())
parent = [0] * (g + 1) # 부모 테이블 초기화
# 부모 테이블상에서, 부모를 자기 자신으로 초기화
for i in range(1, g + 1):
parent[i] = i
result = 0
for _ in range(p):
data = find_parent(parent, int(input())) # 현재 비행기의 탑승구의 루트 확인
if data == 0: # 현재 루트가 0이라면, 종료
break
union_parent(parent, data, data - 1) # 그렇지 않다면 바로 왼쪽의 집합과 합치기
result += 1
print(result)
3. 어두운 길
- 원래 비용 -> 이후비용
- 신장 트리
- 크루스칼 알고리즘
- 신장 트리
# 특정 원소가 속한 집합을 찾기
def find_parent(parent, x):
# 루트 노드가 아니라면, 루트 노드를 찾을 때까지 재귀적으로 호출
if parent[x] != x:
parent[x] = find_parent(parent, parent[x])
return parent[x]
# 두 원소가 속한 집합을 합치기
def union_parent(parent, a, b):
a = find_parent(parent, a)
b = find_parent(parent, b)
if a < b:
parent[b] = a
else:
parent[a] = b
# 노드의 개수와 간선의 개수 입력받기
n, m = map(int, input().split())
parent = [0] * (n + 1) # 부모 테이블 초기화
# 모든 간선을 담을 리스트와, 최종 비용을 담을 변수
edges = []
result = 0
# 부모 테이블상에서, 부모를 자기 자신으로 초기화
for i in range(1, n + 1):
parent[i] = i
# 모든 간선에 대한 정보를 입력받기
for _ in range(m):
x, y, z = map(int, input().split())
# 비용순으로 정렬하기 위해서 튜플의 첫 번째 원소를 비용으로 설정
edges.append((z, x, y))
# 간선을 비용순으로 정렬
edges.sort()
total = 0 # 전체 가로등 비용
# 간선을 하나씩 확인하며
for edge in edges:
cost, a, b = edge
total += cost
# 사이클이 발생하지 않는 경우에만 집합에 포함
if find_parent(parent, a) != find_parent(parent, b):
union_parent(parent, a, b)
result += cost
print(total - result)
4. 행성 터널
- 3차원 최소 신장 트리
- 크루스칼 ElogE
- 간선의 갯수 N(N-1)/2 매우 큼...
- x,y,z축에 대해 정렬 후 각 N-1개의 간선만 고려
- 3*(N-1)
# 특정 원소가 속한 집합을 찾기
def find_parent(parent, x):
# 루트 노드가 아니라면, 루트 노드를 찾을 때까지 재귀적으로 호출
if parent[x] != x:
parent[x] = find_parent(parent, parent[x])
return parent[x]
# 두 원소가 속한 집합을 합치기
def union_parent(parent, a, b):
a = find_parent(parent, a)
b = find_parent(parent, b)
if a < b:
parent[b] = a
else:
parent[a] = b
# 노드의 개수 입력받기
n = int(input())
parent = [0] * (n + 1) # 부모 테이블 초기화
# 모든 간선을 담을 리스트와, 최종 비용을 담을 변수
edges = []
result = 0
# 부모 테이블상에서, 부모를 자기 자신으로 초기화
for i in range(1, n + 1):
parent[i] = i
x = []
y = []
z = []
# 모든 노드에 대한 좌표 값 입력받기
for i in range(1, n + 1):
data = list(map(int, input().split()))
x.append((data[0], i))
y.append((data[1], i))
z.append((data[2], i))
# x,y,z의 min값 하나만 필요
x.sort()
y.sort()
z.sort()
# 인접한 노드들로부터 간선 정보를 추출하여 처리
for i in range(n - 1):
# 비용순으로 정렬하기 위해서 튜플의 첫 번째 원소를 비용으로 설정
edges.append((x[i + 1][0] - x[i][0], x[i][1], x[i + 1][1]))
edges.append((y[i + 1][0] - y[i][0], y[i][1], y[i + 1][1]))
edges.append((z[i + 1][0] - z[i][0], z[i][1], z[i + 1][1]))
# 간선을 비용순으로 정렬
edges.sort()
# 간선을 하나씩 확인하며
for edge in edges:
cost, a, b = edge
# 사이클이 발생하지 않는 경우에만 집합에 포함
if find_parent(parent, a) != find_parent(parent, b):
union_parent(parent, a, b)
result += cost
print(result)
5. 최종 순위
-
정해진 우선순위에 맞게 전체 팀들의 순서를 나열해야 한다
-
이대로 위상 정렬을 수행하게 되면 5-4-3-2-1이 된다.
- 해당 위상 정렬 그래프의 방향을 바꿔준다.
- 1 . 사이클이 발생할 수 있다
- 큐가 빌 경우
- 2 . 위상 정렬 결과가 여러개일 수 있다.
- 큐의 원소가 2개 이상
- 1 . 사이클이 발생할 수 있다
- 1,2가 아니면 위상 정렬 결과는 오직 하나다.
- 해당 위상 정렬 그래프의 방향을 바꿔준다.
from collections import deque
# 테스트 케이스(Test Case)만큼 반복
for tc in range(int(input())):
# 노드의 개수 입력 받기
n = int(input())
# 모든 노드에 대한 진입차수는 0으로 초기화
indegree = [0] * (n + 1)
# 각 노드에 연결된 간선 정보를 담기 위한 인접 행렬 초기화
graph = [[False] * (n + 1) for i in range(n + 1)]
# 작년 순위 정보 입력
data = list(map(int, input().split()))
# 방향 그래프의 간선 정보 초기화
for i in range(n):
for j in range(i + 1, n):
graph[data[i]][data[j]] = True
indegree[data[j]] += 1
# 올해 변경된 순위 정보 입력
m = int(input())
for i in range(m):
a, b = map(int, input().split())
# 간선의 방향 뒤집기
if graph[a][b]:
graph[a][b] = False
graph[b][a] = True
indegree[a] += 1
indegree[b] -= 1
else:
graph[a][b] = True
graph[b][a] = False
indegree[a] -= 1
indegree[b] += 1
# 위상 정렬(Topology Sort) 시작
result = [] # 알고리즘 수행 결과를 담을 리스트
q = deque() # 큐 기능을 위한 deque 라이브러리 사용
# 처음 시작할 때는 진입차수가 0인 노드를 큐에 삽입
for i in range(1, n + 1):
if indegree[i] == 0:
q.append(i)
certain = True # 위상 정렬 결과가 오직 하나인지의 여부
cycle = False # 그래프 내 사이클이 존재하는지 여부
# 정확히 노드의 개수만큼 반복
for i in range(n):
# 큐가 비어 있다면 사이클이 발생했다는 의미
if len(q) == 0:
cycle = True
break
# 큐의 원소가 2개 이상이라면 가능한 정렬 결과가 여러 개라는 의미
if len(q) >= 2:
certain = False
break
# 큐에서 원소 꺼내기
now = q.popleft()
result.append(now)
# 해당 원소와 연결된 노드들의 진입차수에서 1 빼기
for j in range(1, n + 1):
if graph[now][j]:
indegree[j] -= 1
# 새롭게 진입차수가 0이 되는 노드를 큐에 삽입
if indegree[j] == 0:
q.append(j)
# 사이클이 발생하는 경우(일관성이 없는 경우)
if cycle:
print("IMPOSSIBLE")
# 위상 정렬 결과가 여러 개인 경우
elif not certain:
print("?")
# 위상 정렬을 수행한 결과 출력
else:
for i in result:
print(i, end=' ')
print()
반응형
'ETC' 카테고리의 다른 글
네트워크 면접 정리 With 모두의 네트워크. 2장 네트워크의 기본 규칙 (0) | 2021.10.01 |
---|---|
네트워크 면접 정리 With 모두의 네트워크. 1장 네트워크 첫걸음 (0) | 2021.09.30 |
대기업 코딩테스트 준비 7 : 최단 경로 (0) | 2021.02.14 |
대기업 코딩테스트 준비 6 : 다이나믹 프로그래밍 (0) | 2021.02.14 |
대기업 코딩테스트 준비 5 : 이진 탐색 (0) | 2021.02.14 |