풀었던 문제들을 복습
01. 재귀함수
- 정의할 때 자기 자신을 참조하는 함수
- Recursion 오류를 방지하기 위해서 빠져나가는 지점을 만들어야 한다.
- if문을 사용해서 특정 지점을 정해 return을 시켜준다.
- 호출될 때마다 메모리에 스택이 쌓인다.
예제: 하노이의 탑
# 디스크의 수
N = int(input())
# 디스크를 옮기는 재귀함수
# N: 디스크, s: 시작 장대, m: 중간 장대, e: 도착 장대
def moveDisk(N, s, m, e):
# 옮길 디스크가 없을 때
if N == 0:
return
else:
# N - 1 개의 디스크를 중간 장대로 옮긴다.
moveDisk(N - 1, s, e, m)
# 옮긴 디스크와 시작 장대, 도착 장대를 출력
print(N, s, e)
# 중간 장대에 옮긴 디스크를 도착 장대로 옮긴다
moveDisk(N - 1, m, s, e)
moveDisk(N, 1, 2, 3)
02. 이진 탐색
- 순차 탐색과 달리 반씩 후보를 줄이면서 탐색
예제: 백준 2805
# 자르는 높이를 이진 탐색하기
# n: 나무의 수, m: 가지고 갈 나무의 길이
n, m = map(int, input().split())
# 나무의 높이
trees = list(map(int, input().split()))
# 자르는 높이의 시작점
start = 1
# 결과값
res = 0
# 자르는 높이의 끝점
end = max(trees)
def sumCut(mid):
# 잘린 나무의 높이
cut = 0
for tree in trees:
if tree > mid:
cut += tree - mid
return cut
while start <= end:
# 자르는 높이의 중간점(절단기의 현재 높이)
mid = (start + end) // 2
cut = sumCut(mid)
# 더 잘렸거나 맞게 잘랐을 때
if cut >= m:
start = mid + 1
# 최댓값 구하기
if res < mid:
res = mid
# 덜 잘렸을 때
elif cut < m:
end = mid - 1
print(res)
03. 동적 계획법
- 복잡한 문제를 여러 개의 문제로 나누어 푸는 방법
- 하위 문제의 결과를 기록, 그것을 이용 -> 메모이제이션
예제: 백준 11053
가장 긴 증가하는 부분 수열
n = int(input())
arr = list(map(int, input().split()))
arr.insert(0, 0)
# 1번 ~n번 리스트
dy = [0] * (n + 1)
# 각 인덱스에서의 부분수열 길이
dy[1] = 1
# 첫번째 인덱스의 부분 수열 길이
res = 0
# 답
for i in range(2, n + 1): # 각 인덱스 부분 수열 길이 구하기
max = 0
for j in range(i - 1, 0, -1): # i번째 인덱스 앞 탐색
if arr[j] < arr[i] and dy[j] > max:
max = dy[j]
dy[i] = max + 1
if dy[i] > res:
res = dy[i]
print(res)
print(dy)
04. DFS, BFS
- DFS: 깊이 우선 탐색
- 끝까지 파고드는 것
- 공간을 적게 쓰지만 최단 경로를 탐색하기 쉽지 않다.
- 갈 수 있는 만큼 계속 탐색하다가 갈 수 없을 때 다른 방향으로 다시 탐색
- 재귀함수, 스택으로 구현
- BFS: 넓이 우선 탐색
- 모든 분기되는 수를 다 보는 것
- 최단경로를 쉽게 찾는다.
- 공간을 많이 쓰고 시간이 오래걸릴 수 있다.
- 큐로 구현
예제: 백준 1260
# BFS를 사용하기위해 deque 가져오기
from collections import deque
# N: 정점의 개수 , M: 간선의 개수, V: 탐색을 시작할 정점의 번호
N, M, V = map(int, input().split())
# 간선의 개수만큼 입력 받기
# 인접 리스트를 받을 리스트 선언
G = [[] for _ in range(N + 1)]
# 입력받은 정보를 인접 리스트로 변환하기
for _ in range(M):
a, b = map(int, input().split())
G[a].append(b)
G[b].append(a)
# 이차원 리스트의 각 리스트를 오름차순 정렬
for x in G:
x.sort()
# DFS 정의
# 방문한 노드를 기록할 리스트
visit_array_dfs = []
def DFS(cur_node, adj_array, visit_array):
# 현재 노드를 방문 배열에 기록
visit_array.append(cur_node)
# 방문한 노드를 출력
print(cur_node, end=" ")
# 인접 리스트에서 하나씩 탐색
for adj_node in adj_array[cur_node]:
# 현재 노드의 인접 노드가 방문 하지 않았으면
if adj_node not in visit_array_dfs:
# DFS 호출
DFS(adj_node, adj_array, visit_array_dfs)
# 방문한 노드를 기록할 리스트
visit_array_bfs = []
def BFS(satart_node, adj_array):
# 방문할 곳을 기록하는 큐를 출발하는 노드로 초기화
visit_Q = [satart_node]
visit_Q = deque(visit_Q)
# 방문할 곳이 기록된 큐가 빌 때까지 반복
while visit_Q:
# 큐에 가장 먼저 들어온 노드를 현재 노드로 빼기
cur_node = visit_Q.popleft()
# 방문 리스트에 기록
visit_array_bfs.append(cur_node)
# 방문 노드 출력하기
print(cur_node, end=" ")
# 인접 노드 탐색
for adj_node in adj_array[cur_node]:
# 인접 노드가 방문하지 않았으면 큐에 추가
if adj_node not in visit_array_bfs and adj_node not in visit_Q:
visit_Q.append(adj_node)
DFS(V, G, visit_array_dfs)
print()
BFS(V, G)
05. 최단경로
다익스트라 알고리즘
- 특정 노드 -> 다른 모든 노드로 가는 최단 경로 계산
- 음의 간선이 없을 때
- 그리디 알고리즘
- 매 상황에서 가장 비용이 적는 노드 선택
- 출발 노드 -> 최단 거리 테이블 초기화 -> 미방문 노드 중 최단거리 가장 짧은 노드 선택 -> 해당 노드를 거쳐 다른 노드로 가는 비용을 계산 후 최단거리 갱신
- 위를 반복
예제: 백준 1753
import sys
import heapq
# 우선순위 큐 사용
import heapq
INF = float('inf') # 무한을 의미하는 값으로 10억을 설정
# 노드의 개수, 간선의 개수를 입력받기
V, E = map(int, sys.stdin.readline().split())
# 시작 노드 번호를 입력받기
start = int(sys.stdin.readline().split()[0])
# 각 노드에 연결되어 있는 노드에 대한 정보를 담는 리스트를 만들기
graph = [[] for i in range(V + 1)]
# 최단 거리 테이블을 모두 무한으로 초기화
distance = [INF] * (V + 1)
# 모든 간선 정보를 입력받기
for _ in range(E):
u, v, w = map(int, sys.stdin.readline().split())
# a번 노드에서 b번 노드로 가는 비용이 c라는 의미
graph[u].append((v, w))
def dijkstra(start):
q = []
# 시작 노드로 가기 위한 최단 경로는 0으로 설정하여, q에 삽입
# 힙정렬 사용 -> 오름차순 정렬
# 방문하지 않은 노드 중에서 최단거리가 가장 짧은 노드를 선택할 수 있다.
# O(NlogN)
# 큐는 맨 앞의 값을 기준으로 정렬되므로 기존의 순서와바꾸어 입력한다.
heapq.heappush(q, (0, start))
distance[start] = 0
while q: # 큐가 비어있지 않다면
# 가장 최단 거리가 짧은 노드에 대한 정보 꺼내기
dist, now = heapq.heappop(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]))
# 다익스트라 알고리즘을 수행
dijkstra(start)
# 모든 노드로 가기 위한 최단 거리를 출력
for i in range(1, V + 1):
# 도달할 수 없는 경우, INF출력
if distance[i] == INF:
print("INF")
# 도달할 수 있는 경우 거리를 출력
else:
print(distance[i])
플로이드 워셜 알고리즘
- 모든 노드에서 다른 모든 노드까지의 최단 경로 계산
- 단계별로 거쳐가는 노드를 기준으로 알고리즘 수행
- 다이나믹 프로그래밍 유형
구현
INF = int(1e9) # 무한을 의미하는 값으로 10억을 설정
# 노드의 개수 및 간선의 개수를 입력받기
n = int(input())
m = int(input())
# 2차원 리스트(그래프 표현)를 만들고, 모든 값을 무한으로 초기화
graph = [[INF] * (n + 1) for _ in range(n + 1)]
# 자기 자신에서 자기 자신으로 가는 비용은 0으로 초기화
for a in range(1, n + 1):
for b in range(1, n + 1):
if a == b:
graph[a][b] = 0
# 각 간선에 대한 정보를 입력받아, 그 값으로 초기화
for _ in range(m):
# A에서 B로 가는 비용은 C라고 설정
a, b, c = map(int, input().split())
# 가장 짧은 간선 정보만 저장
if c < graph[a][b]:
graph[a][b] = c
# 점화식에 따라 플로이드 워셜 알고리즘을 수행
for k in range(1, n + 1):
for a in range(1, n + 1):
for b in range(1, n + 1):
graph[a][b] = min(graph[a][b], graph[a][k] + graph[k][b])
# 수행된 결과를 출력
for a in range(1, n + 1):
for b in range(1, n + 1):
# 도달할 수 없는 경우, 무한(INFINITY) 출력
if graph[a][b] == INF:
print("INFINITY", end=" ")
# 도달할 수 있는 경우 거리를 출력
else:
print(graph[a][b], end=" ")
print()
'programming study > 항해99 커리큘럼' 카테고리의 다른 글
[항해99 1기] [Chapter3-1] 주특기 기본 - 프론트의 꽃 리액트 (1) (2021.3.19) (0) | 2021.03.19 |
---|---|
[항해99 1기] [Chapter2-2] 자료구조, 알고리즘 (17) (2021.3.19) (0) | 2021.03.19 |
[항해99 1기] [Chapter2-2] 자료구조, 알고리즘 (15) (2021.3.17) (0) | 2021.03.17 |
[항해99 1기] [Chapter2-2] 자료구조, 알고리즘 (14) (2021.3.16) (0) | 2021.03.16 |
[항해99 1기] [Chapter2-2] 자료구조, 알고리즘 (13) (2021.3.15) (0) | 2021.03.15 |