[ 특징 ]
그래프 완전 탐색 기법으로 모든 노드를 검색한다.
시작노드에서 탐색할 쪽의 분기를 정해 최대깊이까지 탐색 후 다른 쪽 분기로 이동해 다시 탐색 = 넓게 탐색 전 깊이 탐색
재귀함수 or 스택을 이용하여 구현 - 순환 알고리즘의 형태
[ 시간복잡도 ]
인접리스트로 표현된 경우 O(V+E) [ V : 노드 수 , E : 에지 수 ]
인접행렬로 표현된 경우 O(N^2)
장점 : 단지 현 경로상의 노드만을 기억하면 되므로 저장공간의 수요가 비교적 적다.
: 목표노드가 깊은 단계에 있을 경우 해를 빨리 구할 수 있다.
단점 : 단순 검색 속도는 BFS에 비하면 느리다.
: 얻어진 해가 최단 경로가 된다는 보장이 없다.
이는 목표에 이르는 경로가 다수인 문제에 대해 DFS는 해에 다다르면 탐색을 끝내버리므로,
이때 얻어진 해는 최적이 아닐 수도 있다.
[ 구현시 고려해야 하는 점 ]
한 번 방문한 노드에 다시 방문하지 않는다
-> 방문 여부를 체크할 배열이 필요하며 체크하지 않을 경우 순환 알고리즘의 형태기에 무한루프에 빠질 위험이 있다.
[ 스택으로 알아보는 탐색 순서 ]
예를 들어 아래의 원본 그래프(유향그래프)가 있을 경우 다음과 같이 인접리스트를 그린다.
또한 방문 체크 배열을 만들어 각 노드들이 재방문되는 것을 방지한다.
탐색 순서를 보면 일단 시작노드를 1로 잡고 1을 push 하고 pop 한다. ( 이때 1의 방문체크배열 값을 true로 변경한다. )
1을 pop 하면서 1과 연결된 2와 3을 push 한다. ( 2,3의 방문체크배열값을 true로 변경한다. )
3을 pop 하면서 3과 연결된 4를 push 한다. ( 4의 방문체크배열값을 true로 변경한다. )
4를 pop하면서 4와 연결된 6을 push 한다. ( 6의 방문체크배열값을 true로 변경한다. )
6을 pop 한다. 6은 연결된 노드가 없기에 push하지않는다.
2를 pop 한다. 2와 연결된 5를 push한다. ( 5의 방문체크배열값을 true로 변경한다. )
5를 pop한다. 5는 연결된 노드가 없기에 push 하지 않는다.
탐색순서 : 1 - 3 - 4- 6 - 2 - 5 ( pop한 순서 )
[ DFS 구현 코드 - 재귀 ]
import java.util.ArrayList;
import java.util.Scanner;
public class DFSMYHAVEDIR {
// 데이터를 넣을 배열
private ArrayList<Integer>[] ll;
// 방문 여부를 체크할 배열
private boolean visited[];
// 노드 수
private int node;
// 배열 초기화
DFSMYHAVEDIR(int node) {
this.node = node;
ll = new ArrayList[this.node + 1];
visited = new boolean[this.node + 1];
for (int i = 1; i < node + 1; i++) {
ll[i] = new ArrayList<Integer>();
}
}
// 엣지 추가
void addEdge(int index, int data) {
ll[index].add(data);
}
// 시작노드가 있는 경우
void DFS(int startnode) {
System.out.print("탐색순서: ");
// 설정한 노드를 가지고 탐색 함수 실행
DFSUtil(startnode);
System.out.println();
}
// 시작노드가 없는 경우
void DFS() {
System.out.print("탐색 순서: ");
// 시작 노드가 없는 경우 1번 노드를 시작노드로 하여 탐색 함수 실행
for (int testNode = 1; testNode < node + 1; testNode++) {
if (!visited[testNode]) {
DFSUtil(testNode);
}
}
System.out.println();
}
void DFSUtil(int testNode) {
// 탐색할 노드가 방문한 적 없는 노드 일 경우 노드를 출력하고 그 노드를 기준으로 다시 탐색시킴
if (!visited[testNode]) {
System.out.print(testNode + " ");
visited[testNode] = true;
for (int i : ll[testNode]) {
if (!visited[i]) {
DFSUtil(i);
}
}
}
}
// 배열 출력
void printfArray() {
System.out.println("-------------------");
for (int i = 1; i < node + 1; i++) {
System.out.print("arraylist[" + i + "] : ");
for (int k : ll[i]) {
System.out.print(k + " ");
}
System.out.println();
}
}
// 시작노드를 1로 설정했기에 둘 다 같은 경로가 나와야한다.
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
DFSMYHAVEDIR myDfs = new DFSMYHAVEDIR(9);
DFSMYHAVEDIR myDfs2 = new DFSMYHAVEDIR(9);
myDfs.addEdge(1, 2);
myDfs.addEdge(1, 3);
myDfs.addEdge(1, 4);
myDfs.addEdge(2, 5);
myDfs.addEdge(2, 6);
myDfs.addEdge(5, 7);
myDfs.addEdge(3, 8);
myDfs.addEdge(4, 8);
myDfs.addEdge(4, 9);
myDfs.addEdge(7, 3);
myDfs.printfArray();
myDfs.DFS(1);
myDfs2.addEdge(1, 2);
myDfs2.addEdge(1, 3);
myDfs2.addEdge(1, 4);
myDfs2.addEdge(2, 5);
myDfs2.addEdge(2, 6);
myDfs2.addEdge(5, 7);
myDfs2.addEdge(3, 8);
myDfs2.addEdge(4, 8);
myDfs2.addEdge(4, 9);
myDfs2.addEdge(7, 3);
myDfs2.printfArray();
myDfs2.DFS();
sc.close();
}
}
현재는 유향그래프이지만 만약 방향이 없는 그래프일 경우 엣지 추가 시 addEdge(1,2)와 addEdge(2,1)처럼 짝을 이뤄 추가해 주거나
아래와 같이 addEdge 함수를 변경해 주면 된다.
( 물론 이 경우엔 index, data로 매개변수를 사용하는 것보단 node1, node2로 이름을 지어주면 좋지 않을까 싶다.)
void addEdge(int index, int data) {
ll[index].add(data);
ll[data].add(index);
}
[ DFS 구현 코드 - 스택 ]
import java.io.*;
import java.util.*;
class Main {
static boolean visited[] = new boolean[9];
static int[][] graph = { {}, { 2, 3, 8 }, { 1, 6, 8 }, { 1, 5 }, { 5, 7 }, { 3, 4, 7 }, { 2 }, { 4, 5 }, { 1, 2 } };
static void dfsStack(int start) {
// 스택생성
Stack<Integer> st = new Stack<Integer>();
// 시작노드 출력 및 스택 삽입
System.out.print(start + " ");
st.push(start);
visited[start] = true;
// 빈스택이 될 때까지 반복
while (!st.isEmpty()) {
int node = st.pop();
for (int i : graph[node]) {
// 방문하지 않은 노드일 경우 스택에 삽입 + 방문배열 값 true로 변경 + 출력
if (!visited[i]) {
visited[i] = true;
System.out.print(i + " ");
st.push(i);
}
}
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//시작 노드 입력받기
int start = sc.nextInt();
dfsStack(start);
}
}
[ 참고문헌 ]
인프런 Do it! 알고리즘 코딩테스트 with JAVA
https://www.inflearn.com/course/lecture?courseSlug=두잇-알고리즘-코딩테스트-자바&unitId=148282&tab=curriculum
블로그
https://gmlwjd9405.github.io/2018/08/14/algorithm-dfs.html
https://better-tomorrow.tistory.com/entry/DFS-BFS-이해하기
'알고리즘' 카테고리의 다른 글
[ 알고리즘 ] Next Permutation ( with 백준 10972 ) (2) | 2024.02.26 |
---|---|
[ 알고리즘 ] 순열, 조합, 부분집합 (0) | 2024.02.25 |
[ 알고리즘 ] 너비우선탐색 BFS - JAVA (0) | 2024.02.16 |
[ 알고리즘 ] 브루트 포스 ( brute force ) 알고리즘 (0) | 2023.02.21 |