코딩테스트 - Chapter 10

DaY·2021년 5월 11일
1

코딩테스트

목록 보기
10/13
post-thumbnail

그래프 이론

코딩 테스트에서 자주 등장하는 기타 그래프 이론 공부하기

1. 다양한 그래프 알고리즘

그래프트리
뱡향성방향 그래프 혹은 무방향 그래프방향 그래프
순환성순환 및 비순환비순환
루트 노드 존재 여부루트 노드가 없음루트 노드가 존재
노드 간 관계성부모와 자식 관계 없음부모와 자식 관계
모델의 종류네트워크 모델계층 모델

서로소 집합

  • 공통 원소가 없는 두 집합

서로소 집합 자료구조

  • 서로소 부분 집합들로 나누어진 원소들의 데이터를 처리하기 위한 자료구조
  • unionfind 2개 연산으로 조작
  • union : 2개의 원소가 포함된 집합을 하나의 집합으로 합치는 연산
  • find : 특정한 원소가 속한 집합이 어떤 집합인지 알려주는 연산
  1. union(합집합) 연산을 확인하여, 서로 연결된 두 노드 A, B를 확인
    1. A와 B의 루트 노드 A', B'를 각각 찾는다.
    2. A'를 B'의 부모 노드로 설정한다.
  2. 모든 union(합집합) 연산을 처리할 때까지 1번 과정을 반복한다.

서로소 집합 알고리즘

def find_parent(parent, x) :
    # 루트 노드가 아닌 경우, 루트 노드를 찾을 때까지 재귀 호출
    if parent[x] != x :
        return find_parent(parent, parent[x])
    return x

# 두 원소가 속한 집합 union
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

v, e = map(int, input().split()) # 노드, 간선 개수
parent = [0] * (v + 1) # 부모 테이블 초기화

for i in range(1, v + 1) :
    parent[i] = i

for i in range(e) :
    a, b = map(int, input().split())
    union_parent(parent, a, b)

print('각 원소가 속한 집합 : ', end = '')
for i in range(1, v + 1) :
    print(find_parent(parent, i), end = ' ')

print()

print('부모 테이블 : ', end = '')
for i in range(1, v + 1) :
    print(parent[i], end = ' ')

개선된 서로소 집합 알고리즘

def find_parent(parent, x) :
    # 루트 노드가 아닌 경우, 루트 노드를 찾을 때까지 재귀 호출
    if parent[x] != x :
        parent[x] = find_parent(parent, parent[x])
    return parent[x]

# 두 원소가 속한 집합 union
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

v, e = map(int, input().split()) # 노드, 간선 개수
parent = [0] * (v + 1) # 부모 테이블 초기화

for i in range(1, v + 1) :
    parent[i] = i # 부모를 자기 자신으로 초기화

for i in range(e) :
    a, b = map(int, input().split())
    union_parent(parent, a, b)

print('각 원소가 속한 집합 : ', end = '')
for i in range(1, v + 1) :
    print(find_parent(parent, i), end = ' ')

print()

print('부모 테이블 : ', end = '')
for i in range(1, v + 1) :
    print(parent[i], end = ' ')

서로소 집합을 활용한 사이클 판별

  1. 각 간선을 확인하며 두 노드의 루트 노드를 확인한다.
    1. 루트 노드가 서로 다르다면 두 노드에 대하여 union 연산을 수행한다.
    2. 루트 노드가 서로 같다면 사이클이 발생한 것이다.
  2. 그래프에 포함되어 있는 간선에 대하여 1번 과정을 반복한다.
def find_parent(parent, x) :
    # 루트 노드가 아닌 경우, 루트 노드를 찾을 때까지 재귀 호출
    if parent[x] != x :
        parent[x] = find_parent(parent, parent[x])
    return parent[x]

# 두 원소가 속한 집합 union
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
    
v, e = map(int, input().split()) # 노드, 간선 개수
parent = [0] * (v + 1) # 부모 테이블 초기화

for i in range(1, v + 1) :
    parent[i] = i # 부모를 자기 자신으로 초기화

cycle = False

for i in range(e) :
    a, b = map(int, input().split())
    if find_parent(parent, a) == find_parent(parent, b) :
        cycle = True
        break
    else :
        union_parent(parent, a, b)

if cycle :
    print('사이클 발생')
else :
    print('사이클이 발생하지 않음')

신장 트리

하나의 그래프가 있을 때 모든 노드를 포함하면서 사이클이 존재하지 않는 부분 그래프를 의미

크루스칼 알고리즘 [그리디 알고리즘]

  • 최소 신장 트리 알고리즘
  1. 간선 데이터를 비용에 따라 오름차순으로 정렬한다.
  2. 간선을 하나씩 확인하며 현재의 간선이 사이클을 발생시키는지 확인한다.
    1. 사이클이 발생하지 않는 경우 최소 신장 트리에 포함시킨다.
    2. 사이클이 발생하는 경우 최소 신장 트리에 포함시키지 않는다.
  3. 모든 간선에 대하여 2번의 과정을 반복한다.
def find_parent(parent, x) :
    # 루트 노드가 아닌 경우, 루트 노드를 찾을 때까지 재귀 호출
    if parent[x] != x :
        parent[x] = find_parent(parent, parent[x])
    return parent[x]

# 두 원소가 속한 집합 union
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

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)

위상 정렬

  • 순서가 정해져 있는 일련의 작업을 차례대로 수행해야 할 때 사용
  • 방향 그래프의 모든 노드를 방향성에 거스르지 않도록 순서대로 나열
  • 진입차수 : 특정한 노드로 들어오는 간선의 개수
  1. 진입차수가 0인 노드를 큐에 넣는다.
  2. 큐가 빌 때까지 다음의 과정을 반복한다.
    1. 큐에서 원소를 꺼내 해당 노드에서 출발하는 간선을 그래프에서 제거한다.
    2. 새롭게 진입차수가 0이 된 노드를 큐에 넣는다.
from collections import deque

v, e = map(int, input().split()) # 노드, 간선 개수
indegree = [0] * (v + 1) # 모든 노드 진입차수 0
graph = [[] for i in range(v + 1)] # 연결 리스트 (그래프)

# 방향 그래프의 모든 간선 정보 입력
for _ in range(e) :
    a, b = map(int, input().split())
    graph[a].append(b) # 정점 a에서 b로 이동
    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()

팀 결성

팀 결성

💡 서로소 집합 알고리즘

def find_parent(parent, x) :
    # 루트 노드가 아닌 경우, 루트 노드를 찾을 때까지 재귀 호출
    if parent[x] != x :
        parent[x] = find_parent(parent, parent[x])
    return parent[x]

# 두 원소가 속한 집합 union
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) # 부모 테이블 초기화

for i in range(1, n + 1) :
    parent[i] = i # 부모를 자기 자신으로 초기화

for i in range(m) :
    oper, a, b = map(int, input().split())
    # 합잡합 연산인 경우
    if oper == 0 :
        union_parent(parent, a, b)
    # 찾기 연산인 경우
    elif oper == 1 :
        if find_parent(parent, a) == find_parent(parent, b) :
            print('YES')
        else :
            print("NO")

도시 분할 계획

도시 분할 계획

💡 전체 그래프에서 2개의 최소 신장 트리 생성
💡 크루스칼 알고리즘으로 최소 신장 트리 구성 간선 중 비용이 큰 간선 제거

def find_parent(parent, x) :
    # 루트 노드가 아닌 경우, 루트 노드를 찾을 때까지 재귀 호출
    if parent[x] != x :
        parent[x] = find_parent(parent, parent[x])
    return parent[x]

# 두 원소가 속한 집합 union
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

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)

커리큘럼

커리큘럼

💡 위상 정렬 알고리즘

from collections import deque
import copy

v = int(input()) # 노드의 개수
indegree = [0] * (v + 1) # 모든 노드의 진입차수 0 초기화
graph = [[] for i in range(v + 1)] # 간선 정보 리스트 초기화
time = [0] * (v + 1) # 시간 0으로 초기화

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 -= 1
            # 새롭게 진입차수 0이되는 노드 큐에 삽입
            if indegree[i] == 0 :
                q.append(i)

    for i in range(1, v + 1) :
        print(result[i])

topology_sort()

0개의 댓글