[알고리즘] DFS / BFS + 백준 10828 10845 17478 1388 2606 1260 1325 2138
선행 개념
스택, 큐, 재귀 개념을 먼저 공부하고 들어가자.
Stack
스택은 박스 쌓기로 생각하면 편하다.
나중에 쌓인 것이 가장 먼저 들릴 수 있는 것처럼, 이러한 구조를 LIFO(Last In First Out)라 한다.
파이썬에서는 리스트로 스택을 구현할 수 있다.
stack = [] stack.append(1) # [1] stack.append(2) # [1, 2] stack.append(3) # [1, 2, 3] stack.pop() # [1, 2] stack.append(4) # [1, 2, 4]
pop() 메소드는 가장 뒤의 데이터를 제거하고, append는 가장 뒤에 데이터를 삽입한다.
백준 10828 스택
import sys n = int(input()) stack = [] cmd = [sys.stdin.readline().rstrip() for _ in range(n)] for x in cmd: if 'push' in x: x = x.replace('push ', '') num = int(x) stack.append(num) elif x == 'pop': if len(stack) >= 1: print(stack.pop()) else: print(-1) elif x == 'size': print(len(stack)) elif x == 'empty': if len(stack) == 0: print(1) else: print(0) elif x == 'top': if len(stack) >= 1: print(stack[-1]) else: print(-1)
Queue
큐는 줄을 서는 것을 생각하면 된다.
나중에 온 사람이 가장 마지막에 들어가게 되고, 먼저 온 사람이 가장 먼저 들어가게 되는 FIFO(First In First Out) 구조이다.
파이썬의 collections 모듈에서 제공하는 deque로 큐를 구현할 수 있다.
from collections import deque q = deque() q.append(1) # deque([1]) q.append(2) # deque([1, 2]) q.append(3) # deque([1, 2, 3]) q.popleft() # deque([2, 3]) q.append(4) # deque([2, 3, 4])
deque 객체를 list() 메소드를 이용해 리스트로 변경할 수도 있다.
백준 10845 큐
import sys from collections import deque n = int(input()) q = deque() cmd = [sys.stdin.readline().rstrip() for _ in range(n)] for x in cmd: if 'push' in x: num = int(x.replace('push ', '')) q.append(num) elif x == 'pop': if len(q) != 0: print(q.popleft()) else: print(-1) elif x == 'size': print(len(q)) elif x == 'empty': if len(q) == 0: print(1) else: print(0) elif x == 'front': if len(q) > 0: print(q[0]) else: print(-1) elif x == 'back': if len(q) > 0: print(q[-1]) else: print(-1)
Recursive function
재귀 함수는 말 그대로 자기 자신을 호출하는 함수다.
def recursive_function(): print('난 재귀 함수야') recursive_function() >>> 난 재귀 함수야 난 재귀 함수야 난 재귀 함수야 난 재귀 함수야 난 재귀 함수야 난 재귀 함수야 ...
재귀 함수의 종료 조건을 명시해주지 않으면 무한 호출에 빠지므로, 꼭 종료 조건을 명시해주어야 한다!
백준 17478 재귀함수가 뭔가요?
n = int(input()) print("어느 한 컴퓨터공학과 학생이 유명한 교수님을 찾아가 물었다.") def recursive_function(num): if num == 0: return if num > 0: print("_" * 4 * (n-num) + '"재귀함수가 뭔가요?"') print("_" * 4 * (n-num) + '"잘 들어보게. 옛날옛날 한 산 꼭대기에 이세상 모든 지식을 통달한 선인이 있었어.') print("_" * 4 * (n-num) + '마을 사람들은 모두 그 선인에게 수많은 질문을 했고, 모두 지혜롭게 대답해 주었지.') print("_" * 4 * (n-num) + '그의 답은 대부분 옳았다고 하네. 그런데 어느 날, 그 선인에게 한 선비가 찾아와서 물었어."') recursive_function(num-1) recursive_function(n) print("_" * 4 * n + '"재귀함수가 뭔가요?"') print("_" * 4 * n + '"재귀함수는 자기 자신을 호출하는 함수라네"') for i in range(n, -1, -1): print("_" * 4 * i + "라고 답변하였지.")
일단 정답은 맞는 코든데... 뭔가 더 좋은 방법이 있을거 같지만 DFS/BFS 먼저 공부해보고 담에 돌아와서 수정하는걸로!!
DFS/BFS
DFS(Depth-First Search)
그래프에서 깊은 부분을 우선으로 탐색하는 알고리즘.
여기서 말하는 그래프란? 노드와 간선으로 표현되는 아래 그림과 같은 것이다.

이러한 그래프는 파이썬으로 두가지 방식을 통해 나타낼 수 있다.
- 인접 행렬(Adjacency Matrix): 2차원 배열로 그래프의 연결 관계를 표현
- 인접 리스트(Adjacency List): 리스트로 그래프의 연결 관계를 표현
인접 행렬(Adjacency Matrix)

연결이 되어있지 않은 노드끼리는 무한의 비용이라 가정하고, 그래프를 인접 행렬 방식으로 코드를 작성해보자.
INF = 999999 graph = [ [0, 7, 5], [7, 0, INF], [5, INF, 0] ]
인접 리스트(Adjacency List)
흔히 linked list를 이용해 구현하는데, 파이썬은 그냥 리스트 쓰면 된다.
graph = [[] for _ in range(3)] graph[0].append((1,7)) graph[0].append((2,5)) graph[1].append((0,7)) graph[2].append((0,5)) print(graph) >>> [[(1, 7), (2, 5)], [(0, 7)], [(0, 5)]]
인접 행렬 방식은 모든 관계를 저장 / 인접 리스트 방식은 연결 정보만을 저장 -> 메모리 효율 ↑ 연결 정보 얻는 속도 ↓
특정한 노드와 연결된 모든 인접 노드를 순회해야 하는 경우, 인접 리스트 방식이 메모리 효율이 더 좋다.
DFS = 깊이 우선 탐색 = 특정 상황에서 최대한 깊이 들어가 노드를 방문한 후, 다시 돌아가 다른 경로를 탐색하는 알고리즘
1) 탐색 시작 노드를 스택에 삽입 후 방문 처리
2) 스택의 최상단 노드에 방문하지 않은 인접 노드가 있으면 그 인접 노드를 스택에 넣고 방문 처리, 방문하지 않은 인접 노드가 없으면 스택에서 최상단 노드 pop
3) 반복
뭔 소린가 싶다.... 난 바보다...... 코드로 자세히 알아보자
def dfs(graph, v, visited): visited[v] = True print(v, end = ' ') for i in graph[v]: if not visited[i]: dfs(graph, i, visited)
뭔 소린가 싶은데... 코드를 하나씩 해석해보자.
v는 현재 노드고, 방문했으니까 방문 노드를 방문 처리 -> True
그리고 graph의 현재 노드와 연결된 다른 노드들을 재귀적으로 방문한다.
BFS(Breadth First Search)
너비 우선 탐색 - 가까운 노드부터 탐색하는 방식
동작 방식은 다음과 같다.
1) 탐색 시작 노드를 큐에 삽입 후 방문처리
2) 큐에서 노드를 pop, pop된 노드의 인접 노드중 방문하지 않은 노드를 모두 큐에 삽입
3) 반복
from collections import deque def bfs(graph, start, visited): q = deque([start]) visited[start] = True #현재 노드 방문처리 while q: v = q.popleft() print(v, end = ' ') for i in graph[v]: if not visited[i]: q.append(i) visited[i] = True
백준 1388 바닥 장식
n, m = map(int, input().split()) graph = [] for i in range(n): s = input() graph.append(list(s)) def dfs_h(x, y): if x <= -1 or x >= n or y <= -1 or y >= m: return False if graph[x][y] == '-': graph[x][y] = 1 dfs_h(x, y+1) dfs_h(x, y-1) return True def dfs_v(x, y): if x <= -1 or x >= n or y <= -1 or y >= m: return False if graph[x][y] == '|': graph[x][y] = 1 dfs_v(x-1, y) dfs_v(x+1, y) return True return False a1 = 0 a2 = 0 for i in range(n): for j in range(m): if dfs_h(i, j) == True: a1 += 1 if dfs_v(i, j) == True: a2 += 1 print(a1 + a2)
DFS를 사용하여 풀어봤다. -인 경우와 |인 경우를 나누어 두 dfs 함수를 구현하여 풀었는데
맞는 건지는 모르겠지만 쨌든 통과는 했다.
백준 2606 바이러스
from collections import deque c = int(input()) n = int(input()) tmp = [] for i in range(n): k = list(map(int, input().split())) tmp.append(k) tmp.append(k[::-1]) tmp = sorted(tmp, key = lambda x:x[0]) graph = [[]] for i in range(1, c+1): temp = [] for x in tmp: if x[0] == i: temp.append(x[1]) graph.append(temp) def bfs(graph, start, visited): q = deque([start]) visited[start] = True while q: v = q.popleft() for i in graph[v]: if not visited[i]: q.append(i) visited[i] = True print(sum(visited)-1) visited = [False] * (c+1) bfs(graph, 1, visited)
백준 1260 DFS와 BFS
from collections import deque n, m, l = map(int, input().split()) def dfs(graph, v, visited): visited[v] = True print(v, end = ' ') for i in graph[v]: if not visited[i]: dfs(graph, i, visited) def bfs(graph, start, visited): q = deque([start]) visited[start] = True while q: v = q.popleft() print(v, end = ' ') for i in graph[v]: if not visited[i]: q.append(i) visited[i] = True tmp = [] for _ in range(m): k = list(map(int, input().split())) tmp.append(k) tmp.append(k[::-1]) graph = [[]]*(n+1) tmp = sorted(tmp, key = lambda y:(y[0], y[1])) for i in range(1, n+1): temp = [] for x in tmp: if x[0] == i: temp.append(x[1]) graph[i] = temp visited1 = [False] * (n+1) visited2 = [False] * (n+1) dfs(graph, l, visited1) print("") bfs(graph, l, visited2)
백준 1325 효율적인 해킹
첨에 BFS 하면 엄청 편하겠다 싶었는데 시간 초과ㅏㅏㅏㅏㅏㅏㅏㅏㅏ
from collections import deque import sys n, m = map(int, input().split()) graph = [[]]*(n+1) tmp = [0] * m for i in range(m): temp = list(map(int, sys.stdin.readline().rstrip().split())) tmp[i] = temp[::-1] tmp = sorted(tmp, key = lambda x: x[0]) for i in range(1, n+1): temp = [] for x in tmp: if x[0] == i: temp.append(x[1]) graph[i] = temp def bfs(graph, start, visited): q = deque([start]) visited[start] = True cnt = 0 while q: v = q.popleft() cnt += 1 for i in graph[v]: if not visited[i]: q.append(i) visited[i] = True return cnt cntlist = [0]*(n+1) for x in range(1, n+1): visited = [False] * (n+1) cntlist[x] = bfs(graph, x, visited) for i in range(1, n+1): if cntlist[i] == max(cntlist): print(i, end = ' ')
이건 시간 초과되어버린 내 코드다... 어떻게 시간초과를 해결할 수 있을까.........
from collections import deque n, m = map(int, input().split()) graph = [[] for _ in range(n+1)] for _ in range(m): i, j = map(int, input().split()) graph[j].append(i) def bfs(graph, start, visited): q = deque([start]) visited[start] = True cnt = 1 while q: v = q.popleft() for i in graph[v]: if not visited[i]: q.append(i) visited[i] = True cnt += 1 return cnt max_cnt = 0 result = [] for x in range(1, n+1): visited = [False] * (n+1) cnt = bfs(graph, x, visited) if cnt > max_cnt: max_cnt = cnt result.append([x, cnt]) for idx, cnt in result: if cnt == max_cnt: print(idx, end = ' ')
그래프 입력받는 과정을 좀 간소화했더니 풀렸다. 허무ㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜ해
백준 2138 전구와 스위치 - 복습문제
n = int(input()) current = input() want = input() change = {'00': '11', '01': '10', '10': '01', '11': '00', '000': '111', '001': '110', '010': '101', '011': '100', '100': '011', '101': '010', '110': '001', '111': '000'} tmp = current tmp2 = current cnt = 0 cnt2 = 1 # 0번스위치 안눌러 for s in range(1,n-1): if tmp[s-1] != want[s-1]: cnt += 1 tmp = tmp.replace(tmp[s-1:s+2], change[tmp[s-1:s+2]]) if tmp[n-1] != want[n-1]: cnt += 1 tmp = tmp.replace(tmp[n-2:], change[tmp[n-2:]]) if tmp != want: cnt = 0 # 0번을 누르고 시작 tmp2 = tmp2.replace(tmp2[:2], change[tmp2[:2]]) for s in range(1,n-1): if tmp2[s-1] != want[s-1]: cnt2 += 1 tmp2 = tmp2.replace(tmp2[s-1:s+2], change[tmp2[s-1:s+2]]) if tmp[n-1] != want[n-1]: cnt2 += 1 tmp2 = tmp2.replace(tmp2[n-2:], change[tmp2[n-2:]]) if tmp != want and tmp2 != want: print(-1) else: if tmp == want and tmp2 == want: print(min(cnt, cnt2)) else: if tmp == want: print(cnt) else: print(cnt2)
시반 초과가 나왔다.............................................. 진짜 열심히 풀었는데......................................
근데 코드도 다시 보니까 틀렸더라 ㅋㅋ
ㅋㅋ
ㅋㅋ
...
그래서 수정 열심히 해서 코드 다시 짰다. 성공~~~~~~~~~~~~~ sys 사용해서 조금이라도 빠르게 하려고 노력함
import sys n = int(input()) current = sys.stdin.readline().rstrip() want = sys.stdin.readline().rstrip() change = {'00': '11', '01': '10', '10': '01', '11': '00', '000': '111', '001': '110', '010': '101', '011': '100', '100': '011', '101': '010', '110': '001', '111': '000'} tmp = current tmp2 = current cnt = 0 cnt2 = 1 # 0번스위치 안눌러 for s in range(1,n-1): if tmp[s-1] != want[s-1]: cnt += 1 tmp = tmp[:s-1] + change[tmp[s-1:s+2]] + tmp[s+2:] if tmp[n-1] != want[n-1]: cnt += 1 tmp = tmp[:n-2] + change[tmp[n-2:]] if tmp != want: cnt = 0 # 0번을 누르고 시작 tmp2 = change[tmp2[:2]] + tmp2[2:] for s in range(1,n-1): if tmp2[s-1] != want[s-1]: cnt2 += 1 tmp2 = tmp2[:s-1] + change[tmp2[s-1:s+2]] + tmp2[s+2:] if tmp2[n-1] != want[n-1]: cnt2 += 1 tmp2 = tmp2[:n-2] + change[tmp2[n-2:]] if tmp != want and tmp2 != want: print(-1) else: if tmp == want and tmp2 == want: print(min(cnt, cnt2)) else: if tmp == want: print(cnt) else: print(cnt2)
'알고리즘' 카테고리의 다른 글
[알고리즘] DP(Dynamic Programming) - 백준 17202 9655 2579 2156 2565 1967 3020 (1) | 2023.03.04 |
---|---|
[알고리즘] 이진 탐색(Binary Search) - 백준 10816 2512 2805 16564 2467 1339 7576 (0) | 2023.03.01 |
[알고리즘] 정렬 - 백준 2750 1920 2108 1946 2470 1461 10026 (0) | 2023.02.28 |
[알고리즘] 구현 - 백준 1009 2563 11655 18111 18405 1092 (그리디) (1) | 2023.02.23 |
[알고리즘] Greedy (탐욕법) - 백준 10162 11399 1541 1931 1715 (0) | 2023.02.23 |