백준 26170을 시도했다 뭔가 이상함을 느끼며 실패했고 백준 25418번을 푸는데 있어 이상함이 정확하다 판단했고 구체적으로 DFS, BFS의 구현에 대해 집고 넘어가기 위해 글을 작성한다
이상함의 이유는 2차원 배열 형태에서 거리를 탐색하는데 있어 BFS와 DFS의 차이점을 구분짓지 않고 DFS로 풀었기 때문이었다
DFS와 BFS의 가장 큰 차이점으로 QUEUE, STACK 형태로 알고 있었고 코드를 구현하는데 있어 QUEUE와 STACK의 자료구조를 알고있는 것 만으로도 충분하다 생각했지만 얕은 생각이었고 프로그램의 구현 방식으로도 QUEUE와 STACK처럼 이용할 수 있다는 것을 알 수 있었다
arr = [[0] * 5 for i in range(5)]
check = [[False] * 5 for i in range(5)]
def check_dist(x,y,dist):
global arr
if (0 <= x and x < len(arr)) and (0 <= y and y < len(arr)):
if not check[x][y]:
arr[x][y] = dist
check[x][y] = True
dist += 1
check_dist(x+1, y, dist)
check_dist(x, y+1, dist)
check_dist(x-1, y, dist)
check_dist(x, y-1, dist)
else:
return
check_dist(2,2,0)
for i in arr:
print(i)
import java.util.*;
public class Main {
public static void main(String[] args){
Stack<Integer> stack = new Stack<>();
boolean[] visited;
int[][] graph = {
{}
,{2, 3, 8}
,{1, 7}
,{1, 4, 5}
,{3, 5}
,{3, 4}
,{7}
,{2, 6, 8}
,{1, 7}
};
visited = new boolean[graph.length];
stack.push(1);
System.out.print(1+" ");
visited[1] = true;
while(stack.size() > 0) {
boolean check = false;
int node = stack.get(stack.size()-1);
for(int i = 0; i<graph[node].length; i++){
if (!visited[graph[node][i]]) {
stack.push(graph[node][i]);
System.out.print(graph[node][i]+" ");
visited[graph[node][i]] = true;
check = true;
break;
}
}
if (!check) {
stack.pop();
}
}
}
}
1 2 7 6 8 3 4 5
Stack을 사용한 경우이며 처음 시작 노드를 넣고 시작 노드의 Graph에서 방문하지 않은 노드가 있는 경우 스택에 추가, 방문하지 않은 노드가 없는 경우는 pop() 이용해 진행
깊이가 우선이기에 방문하지 않은 노드가 있는 경우 추가하고 for 반복문을 이탈하며 방문하지 않은 노드가 있고 없고는 스택에 방문하지 않은 경우는 check 변수를 true로 바꿔 확인
Stack 이용한 경우와 같은 Graph
import java.util.*;
public class Main {
static void dfs(int[][] graph, int node, boolean[] visited){
visited[node] = true;
System.out.print(node + " ");
for(int i = 0; i<graph[node].length; i++){
if(!visited[graph[node][i]]){
dfs(graph, graph[node][i], visited);
}
}
}
public static void main(String[] args){
boolean[] visited;
int[][] graph = {
{}
,{2, 3, 8}
,{1, 7}
,{1, 4, 5}
,{3, 5}
,{3, 4}
,{7}
,{2, 6, 8}
,{1, 7}
};
visited = new boolean[graph.length];
dfs(graph, 1, visited);
}
}
1 2 7 6 8 3 4 5
우선 특징은 Stack을 이용한 경우보다 굉장히 간결하다
재귀는 DFS 이론 자체처럼 기준 노드에서 방문하지 않은 노드가 존재하는 경우 DFS 재귀 호출하는 방식으로 진행된다
구체적으로
1=> 2 => 7 => 6
과정을 처음으로 진행하고
6
노드에서 더 이상 방문하지 않은 노드가 없기에 이 전 상황으로 돌아가
7
노드에서 for6, 8
형태에서6
다음8
번 노드를 방문해1=> 2 => 7 => 8
로 진행하며
8
번 기준으로 모두 방문했기에2
,
마찬가지로2
노드도 모두 방문했기에
1
번 노드로 돌아가1 => 3 => 4 => 5
과정을 거친다
재귀 = 반복문의 중첩이라 생각하면 이해하기 쉬울 것 같다
for
for
for
재귀의 경우 프로그램 종료를 위해 종료 조건이 의무적으로 필요한데 for문을 이용해 재귀를 선언했기에 for문 종료 = 종료로 따로 정의해주지 않아도 된다
위에 진행했던 기본적인 DFS의 경우 노드들의 조건을 따지는 것이 아닌 모든 경우의 수를 탐색하는 것
단순히 for문이 종료되며 재귀가 종료되는 방식이다
백트래킹의 경우는 한정 조건
이란 것을 가지며 한정 조건을 만족하는 모든 경우의 수를 탐색하는데 목적이 있다
한정 조건
을 따지고 만족하지 않은 경우 이전 상황으로 복귀하며 탐색하는 구조프로그램 구조는 재귀를 이용해 진행했을 경우 재귀 함수 내부의 재귀 종료 조건
, 한정 조건
두 가지를 이용해 문제에 상황에 맞게 진행하면 된다
코드는 해당 문제를 참고하면 될 것 (https://velog.io/@sung-ik-je/%EB%B0%B1%EC%A4%80-26169)
백트래킹을 사용하는데 있어 유의해야 될 점은 한정 조건
을 이용해 경로를 설정하다 현 경로가 올바르지 않은 경우 다시 복귀하게 되는데 이 때, 즉 dfs 탐색이 끝난 후 한정 조건
을 다시 수정해야한다
한정 조건
을 만족하지 않도록 수정한다한정 조건
에 만족하게 다시 초기화 시킨다DFS의 경우 완전 탐색이라 생각하면 된다
때문에 조건을 계속해서 고려하기 때문에 계속해서 함수를 호출하며 가지치기 필요 X
단순히 함수를 호출하며 탐색하기만 하면 된다
하지만 백트래킹의 경우 가지치기가 필요
즉, 현재 경로가 틀렸다고 생각한 경우 이전 경로로 복귀해 다른 가지로 작업이 필요하기 때문에 방문 표시 유무를 핸들링 할 필요 존재
ex) 순열, 조합의 경우 재귀, 백트래킹 2개의 구현 방법이 있는데 백트래킹을 사용한 경우는 가지치기를 목적으로 방문 표시를 핸들링 할 필요가 있다
score = [[0 for _ in range(5)] for _ in range(5)]
visit = [[False for _ in range(5)] for _ in range(5)]
q = deque()
def bfs(x,y,z):
if (0 <= x and x < 5) and (0 <= y and y < 5):
if visit[x][y] == False:
score[x][y] = z
visit[x][y] = True
q.append([x+1, y, z+1])
q.append([x, y+1, z+1])
q.append([x-1, y, z+1])
q.append([x, y-1, z+1])
while len(q) != 0:
x2, y2, z2 = q.popleft()
bfs(x2, y2, z2)
else:
return
bfs(2,2,0)
for i in score:
print(i)
재귀
로 프로그램 이어가지만 BFS의 경우는 queue 돌리고 재귀
로 문제 해결 DFS graph와 동일
import java.util.*;
public class Main {
public static void main(String[] args){
boolean[] visited;
int[][] graph = {
{}
,{2, 3, 8}
,{1, 7}
,{1, 4, 5}
,{3, 5}
,{3, 4}
,{7}
,{2, 6, 8}
,{1, 7}
};
visited = new boolean[graph.length];
Queue<Integer> queue = new LinkedList<>();
queue.add(1);
visited[1] = true;
while(queue.size() > 0){
int node = queue.poll();
System.out.print(node + " ");
for(int i = 0; i<graph[node].length; i++){
if(!visited[graph[node][i]]){
queue.add(graph[node][i]);
visited[graph[node][i]] = true;
}
}
}
}
}
1 2 3 8 7 4 5 6
- 처음 큐에 시작 노드를 집어 넣고 시작
- 큐의 맨 앞 노드를 꺼내 graph 상에 방문하지 않은 노드들 모두 큐에 추가
1, 2번 반복