Java/코딩테스트

[ DFS / BFS ] 개념 및 예제 정리

Bubbles 2023. 4. 5. 18:54

코딩테스트에서 자주 사용되는 완전 탐색 기법 DFS/BFS 정리

자주 나오기도 하고... 중요하기도 해서 복습겸 정리하는 포스팅!


DFS 

🌷특징 및 언제 사용? 

- 탐색 공간 자체는 크지만 깊이가 유한할 때(공간 복잡도가 작다)

- 최단 경로가 아니어도 괜찮을 때

- 재귀함수로 구현 -> 현재 검사하는 상태에서 전이할 수 있는 상태라면 해당 상태로 전이

- 시작하는 부분에서 한쪽 분기 정하고 최대 깊이까지 탐색 후 다른 분기로 이동, 다시 최대 깊이까지 탐색 ... 

- 단절점 찾기, 단절선 찾기, 위상정렬, 사이클 찾기 등.. 

 

🌷기본 코드 구현 시 필요한 것 

- 방문 여부를 저장할 방문 여부 배열

- 재귀함수로 구현하거나 스택 활용! 

 

🌷 로직  

1. while (스택에 값이 있는 동안)

2. 방문 여부 검사

3. state 변수(현재 방문한 변수)에 대한 처리(문제마다 다름)

4. 다음에 방문해야할 자료에 대해 범위, 방문 여부 등 문제에 따라 조건 검사

5. 조건에 위배되지 않으면 스택에 push or 재귀 함수 호출

boolean isVisited[] = new boolean[n];
Stack<Integer> stack = new Stack<>();

while (!stack.isEmpty()) {
    int state = stack.pop();
    if (isVisited[]) {
    	// continue 또는 리턴 
    }
    isVisted[state] = true;
    for (int next : getNext(state)) {
    	if (!isVisted[next]) {
        	stack.push(next);
        }
    }
}

// 재귀로 구현한다면 
static void DFS(int node) {
	if (visited[node]) {
		return ;
    } // 이미 방문한 노드인 경우엔 수행 X
    visited[node] = true; // 한 번 지난 노드는 다시 안 지나가게끔
    for (int i : graph[node]) {
    	if (!visited[i]) {
        	DFS(i);
        }
    }
}

 

 

🌷프로그래머스 lv3 네트워크

https://school.programmers.co.kr/learn/courses/30/lessons/43162

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

DFS긴 DFS인데 처음에 구하려는 답을 내가 잘못 생각했다. 문제이해를 잘못했었는데, 제대로 이해해도 못풀어서 해설을 보고 코드를 짰다ㅎ... 

 여기서의 visited는 각 컴퓨터별로 연결된거 모두 탐색해봤는지를 저장하는 배열이다. 이 visited를 계속 DFS함수 매개변수로 넣어주고, 한 컴퓨터당 탐색이 끝나면 answer++해준다. 

어려운 문젠 아닌거같은데 아직 부족하군...허허 

 

import java.util.*;
class Solution {
    public int solution(int n, int[][] computers) {
        boolean[] visited = new boolean[n];
        int answer = 0;
        for (int i = 0; i < n; i++) {
            if (visited[i]) {
                continue;
            }
            DFS(i, computers, visited);
            answer++;
        }
        return answer;
    }
    
    void DFS(int now, int[][] computers, boolean[] visited) {
        Stack<Integer> stack = new Stack<>(); 
        stack.push(now);
        
        while (!stack.isEmpty()) {
            int temp = stack.pop();
            if (visited[temp]) {
                continue;
            }
            visited[temp] = true;
            for (int i = 0; i < computers[temp].length; i++) {
                if (computers[temp][i] == 0) {
                    continue;
                }
                if (temp != i) {
                    stack.push(i);
                }
            }
        }
    }
}

 


BFS 

🌷특징 및 언제 사용? 

- 초기 상태에서 가까운 상태부터 탐색

- 최단 거리나 최단 시간 등 목표 상태에서 도달할 수 있는 가장 빠른 경로 탐색 

- 탐색 공간의 깊이 제약이 없는 편이나 공간복잡도가 크다 

- 큐를 사용, 큐가 탐색 공간을 나타내고 큐에 다음 탐색할 것들을 추가하는 방식으로 진행 .. 

 

🌷DFS와 차이점? 

- 스택 대신 큐를 사용 

- 큐에 넣어줄 때 방문 처리를 한다는 점

boolean[] isVisited = new boolean[n];
Queue<Integer>() queue = new LinkedList<>();
queue.add(0); //초기 상태 저장
isVisited[0] = true;

while (!queue.isEmpty()) {
	int now = queue.poll();
    for (int next : getNextStates(now)) {
    	// 방문 여부 외에 추가할만한 검사 조건들 추가하기 
    	if (isVisted[next]) {
        	continue;
        }
    }
    isVisited[next] = true;
    queue.add(next);
}

 static void BFS(int num) {
        Queue<Integer> queue = new LinkedList<Integer>();
        queue.add(num);
        isVisited[num] = true;

        while(!queue.isEmpty()) {
            int now = queue.poll();
            System.out.print(now + " ");
            for (int i : graph[now]) {
                if (!isVisited[i]) {
                    isVisited[i] = true;
                    queue.add(i);
                }
            }
        }
    }

 

 

🌷프로그래머스 level3 단어변환

https://school.programmers.co.kr/learn/courses/30/lessons/43163

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

🌷로직 

- 하나의 문자만 다른지 검사(word1 -> word2 바꿀 수 있는지 여부 검사하는 함수)

     -> boolean canChange(String word1, String word2) 

- 바꿀 수 있는 단어들을 다 큐에 넣어두고 탐색 수행 (BFS)

- 큐에서 꺼낼 때마다 target과 같은 문자열인지 확인, 맞다면 답 리턴

- 큐가 다 비었는데도 리턴되지 않았다 => 전환이 불가능하다는 뜻이므로 0 리턴 

import java.util.*;
class Solution {
    public int solution(String begin, String target, String[] words) {
        Queue<Word> queue = new LinkedList<>(); 
        boolean[] visited = new boolean[words.length];
        queue.add(new Word(begin, 0));
    
        while (!queue.isEmpty()) {
            Word w = queue.poll();
            if (w.st.equals(target)) {
                return w.count; // 꺼냈는데 target과 같다면 답 리턴
            }
            for (int i = 0; i < words.length; i++) {
                if (!visited[i]) {
                    if (canChange(w.st, words[i])) {
                        visited[i] = true;
                        queue.add(new Word(words[i], w.count+1));
                    }
                }
            }
        }
        return 0; // 다 돌아도 답 안나오는 경우 
    }
    
    public boolean canChange(String word1, String word2) {
        int count = 0;
        char[] first = word1.toCharArray();
        char[] second = word2.toCharArray();
        for (int i = 0; i < word1.length(); i++) {
            if (first[i] != second[i]) {
                count++;
            }
            if (count > 1) {
                return false;
            }
        }
        return true;
    }
    
    class Word {
        String st;
        int count;
        public Word(String s, int c) {
            this.st = s;
            this.count = c;
        }
    }
}