코딩테스트 낙서장2
지난 한 주 의문의 잠수를 타면서 뇌가 깨끗해졌습니다. 그로 인해 주요 개념들을 다시 복습하고자 글을 새로 작성했습니다. 이 글에는 주요 개념만 간단하게 채워나갈 예정입니다.
그리디
- 그리디는 현재 상황에서 가장 좋아 보이는 것만을 선택하는 알고리즘으로, 탐욕적으로 문제를 푸는 알고리즘입니다.
# 그리디 알고리즘: 거스름돈
coins = [500, 100, 50, 10]
n = 1260
cnt = 0
for coin in coins:
cnt += n // coin
n %= coin
print(cnt)
구현
- 구현은 머릿속에 있는 알고리즘을 소스코드로 바꾸는 과정입니다. 세부적으로 보면 완전 탐색은 모든 경우의 수를 전부 다 계산하는 문제 유형이고, 시뮬레이션은 문제에서 제시한 알고리즘을 한 단계씩 차례대로 직접 수행하는 문제 유형입니다.
'''시뮬레이션'''
# L, R, U, D
dx = [+0, +0, -1, +1]
dy = [-1, +1, +0, +0]
p = {
'L': 0,
'R': 1,
'U': 2,
'D': 3
}
def in_range(x, y):
return 0 <= x < n and 0 <= y < n
n = int(input())
x, y = 0, 0
plans = input().split()
for plan in plans:
nx, ny = x + dx[p[plan]], y + dy[p[plan]]
if in_range(nx, ny):
x, y = nx, ny
print(x, y)
'''완전탐색(Brute Force)'''
H = int(input())
cnt = 0
for h in range(H+1):
for m in range(60):
for s in range(60):
if '3' in str(h)+str(m)+str(s):
cnt += 1
print(cnt)
DFS
- DFS(Depth-First Search)는 깊이 우선 탐색으로, 그래프에서 깊은 부분을 우선적으로 탐색하는 알고리즘입니다. 구현에 스택과 재귀 함수를 사용합니다.
def DFS(graph, n, visited):
visited[n] = 1
print(n, end=' ')
for i in graph[n]:
if not visited[i]:
DFS(graph, i, visited)
graph = [
[1, 2, 7],
[0, 6],
[0, 3, 4],
[2, 4],
[2, 3],
[6],
[1, 5, 7],
[0, 6]
]
visited = [0] * 8
DFS(graph, 0, visited)
BFS
- BFS(Breadth First Search)는 너비 우선 탐색으로, 가까운 노드부터 탐색하는 알고리즘입니다. 구현에 큐를 사용합니다.
from collections import deque
def BFS(graph, start, visited):
q = deque()
q.append(start)
visited[start] = 1
while q:
cur = q.popleft()
print(cur, end=' ')
for i in graph[cur]:
if not visited[i]:
q.append(i)
visited[i] = 1
graph = [
[1, 2, 7],
[0, 6],
[0, 3, 4],
[2, 4],
[2, 3],
[6],
[1, 5, 7],
[0, 6]
]
visited = [0] * 8
BFS(graph, 0, visited)
정렬
- 선택 정렬: 가장 작은 데이터를 선택해 맨 앞의 데이터와 바꿔나가는 정렬 방법(N^2)
- 삽입 정렬: 데이터를 하나씩 확인하며, 각 데이터를 적절한 위치에 삽입하는 정렬 방법(N^2)
- 퀵 정렬: 기준 데이터를 설정하고 그 기준보다 큰 데이터와 작은 데이터의 위치를 바꾸는 방식의 정렬 방법(N log N)
- 계수 정렬: 특정한 조건이 성립될 때만 사용할 수 있지만 매우 빠른 정렬 알고리즘(N+K)
- 거품 정렬: 서로 인접한 두 원소의 대소를 비교하여 조건에 맞지 않는 것들의 자리를 교환하는 정렬 방법(N^2)
lst = [7, 5, 9, 0, 3, 1, 6, 2, 4, 8]
# 선택 정렬
def selection_sort(lst):
for i in range(len(lst)):
minidx = i
for j in range(i+1, len(lst)):
if lst[minidx] > lst[j]:
minidx = j
lst[i], lst[minidx] = lst[minidx], lst[i]
# 삽입 정렬
def insertion_sort(lst):
for i in range(len(lst)):
for j in range(i, 0, -1):
if lst[j] < lst[j-1]:
lst[j], lst[j-1] = lst[j-1], lst[j]
else:
break
# 퀵 정렬
def quick_sort(lst):
if len(lst) <= 1:
return lst
pivot = lst[0]
tail = lst[1:]
lstleft = [x for x in tail if x <= pivot]
lstright = [x for x in tail if pivot < x]
return quick_sort(lstleft) + [pivot] + quick_sort(lstright)
# 계수 정렬
counting = [0] * (max(lst)+1)
def countsort(lst):
for n in lst:
counting[n] += 1
for i in range(len(counting)):
for _ in range(counting[i]):
print(i, end=' ')
# 거품 정렬
def bubble_sort(lst):
for i in range(len(lst)):
for j in range(1, len(lst)-i):
if lst[j-1] > lst[j]:
lst[j-1], lst[j] = lst[j], lst[j-1]
이진 탐색
- 이진 탐색은 O(n)인 순차 탐색보다 효율이 좋은 탐색 기법으로, 정렬되어있는 리스트에 대해 O(log n)의 시간복잡도로 탐색을 할 수 있는 방식입니다.
# 순차 탐색
def sequential_search(lst, target):
for i in range(len(lst)):
if lst[i] == target:
return i
return -1
# 이진 탐색
def binary_search(lst, s, e, target):
if s > e:
return -1
m = (s+e) // 2
if lst[m] == target:
return m
if target < lst[m]:
return binary_search(lst, s, m-1, target)
else:
return binary_search(lst, m+1, e, target)
다이나믹 프로그래밍
- 다이나믹 프로그래밍 기법은 메모리 공간을 조금 더 사용하여 기존 결과를 재사용하는 기법이다. 즉, 큰 문제를 작게 나누고, 같은 문제라면 한 번씩만 풀어 문제를 효율적으로 해결하는 알고리즘 기법이다.
# DP_fibo_Top-down
dp = [0] * 101
def fibo(n):
if n < 2:
return n
if dp[n] != 0:
return dp[n]
dp[n] = fibo(n-2) + fibo(n-1)
return dp[n]
print(fibo(100))
# DP_fibo_Bottom-up
dp = [0] * 101
dp[1] = 1
for i in range(2, 101):
dp[i] = dp[i-2] + dp[i-1]
print(dp[100])
다익스트라 알고리즘
- 다익스트라 최단 경로 알고리즘은 그래프에서 여러 개의 노드가 있을 때, 특정한 노드에서 출발하여 다른 노드로 가는 각각의 최단 경로를 구해주는 알고리즘이다. 다익스트라는 음수인 가중치를 가지는 간선이 없을 때 사용 가능하다.(그리드 알고리즘의 일종)
# 다익스트라 알고리즘
import heapq, sys
from tkinter import N
input = sys.stdin.readline
INF = int(1e9)
n, m = map(int, input().split())
start = int(input())-1
graph = [[] for _ in range(n)]
dist = [INF] * n
for _ in range(m):
s, e, w = map(int, input().split())
graph[s-1].append((e-1, w))
def dijkstra(start):
q = []
heapq.heappush(q, (0, start))
dist[start] = 0
while q:
w, s = heapq.heappop(q)
if dist[s] < w:
continue
for e, nw in graph[s]:
weight = w + nw
if weight < dist[e]:
dist[e] = weight
heapq.heappush(q, (weight, e))
dijkstra(start)
for i in range(n):
print("%d: " % i, end='')
if dist[i] == INF:
print("INF")
else:
print(dist[i])
플로이드 워셜 알고리즘
- 플로이드 워셜 알고리즘은 모든 지점에서 다른 모든 지점까지의 최단 경로를 “모두” 구해야 하는 경우에 사용할 수 있는 알고리즘이다.(다이나믹 프로그래밍 알고리즘의 일종)
# 플로이드 워셜 알고리즘
INF = int(1e9)
n = int(input())
m = int(input())
graph = [
[INF for _ in range(n)]
for _ in range(n)
]
for i in range(n):
graph[i][i] = 0
for _ in range(m):
s, e, w = map(int, input().split())
graph[s-1][e-1] = w
for mid in range(n):
for s in range(n):
for e in range(n):
graph[s][e] = min(graph[s][e], graph[s][mid] + graph[mid][e])
for i in range(n):
for j in range(n):
if graph[i][j] == INF:
print(INF, end=' ')
else:
print(graph[i][j], end=' ')
print()
서로소 집합 알고리즘
- 두 집합이 서로소 관계인 경우 각 집합이 어떤 원소를 공통으로 가지고 있는지 확인할 수 있다. 이를 통해 어떤 노드의 부모 노드(연결된 노드 중 가장 작은 노드)가 같은지를 확인할 수 있다.
# 서로소 집합 알고리즘을 이용한 사이클 판별
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
v, e = map(int, input().split())
parent = [0] * (v)
for i in range(v):
parent[i] = i
iscycle = False
for i in range(e):
a, b = map(int, input().split())
a-=1
b-=1
if find_parent(parent, a) == find_parent(parent, b):
iscycle = True
break
union_parent(parent, a, b)
if iscycle:
print("CYCLE")
for i in range(v):
print(parent[i], end=' ')
크루스칼 알고리즘
- 최소한의 비용으로 신장 트리를 찾을 때 사용한다.
# 크루스칼 알고리즘
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
v, e = map(int, input().split())
parent = [i for i in range(v)]
edges = []
answer = 0
for _ in range(e):
a, b, w = map(int, input().split())
edges.append((w, a-1, b-1))
# 최소 비용부터, 사이클이 생기지 않게 하여, MST를 만들기 위함
edges.sort()
for edge in edges:
w, s, e = edge
if find_parent(parent, s) != find_parent(parent, e):
union_parent(parent, s, e)
answer += w
print(answer)
위상 정렬(Topology Sort)
- 위상 정렬은 방향 그래프의 모든 노드를 방향성에 거스르지 않도록 순서대로 나열하는 것이다.
from collections import deque
# 위상 정렬
v, e = map(int, input().split())
indegree = [0] * v
graph = [[] for _ in range(v)]
for _ in range(e):
a, b = map(int, input().split())
graph[a-1].append(b-1)
indegree[b-1] += 1
def topology_sort():
result = []
q = deque()
for i in range(v):
if indegree[i] == 0:
q.append(i)
while q:
cur = q.popleft()
result.append(cur)
for i in graph[cur]:
indegree[i] -= 1
if indegree[i] == 0:
q.append(i)
for i in result:
print(i, end=' ')
topology_sort()