"더 나아갈 길이 보이지 않을 때까지 깊이 들어간다"를 원칙으로 그래프 내의 정점을 방문하는 알고리즘이다.
미로 찾기처럼 그래프의 정점을 타고 깊이 들어가다가 더 이상 방문해왔던 정점 말고는 다른 이웃을 갖고 있지 않은 정점을 만나면
뒤로 돌아와 다른 경로로 뻗어있는 정점을 타고 방문을 재개하는 방식으로 동작한다.
루트 노드(혹은 다른 임의의 노드)에서 시작해서 다음 분기(branch)로 넘어가기 전에 해당 분기를 완벽하게 탐색하는 방법
사용하는 경우: 모든 노드를 방문하고자 하는 경우에 이 방법을 선택한다. (완전 탐색 알고리즘에 자주 이용됨)
정점의 개수, 간선의 개수, 탐색을 시작할 정점을 번호를 입력받는다.
노드 방문 여부를 검사하기 위한 boolean 배열도 선언한다.
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 정점의 개수
int m = sc.nextInt(); // 간선의 개수
int v = sc.nextInt(); // 탐색을 시작할 정점의 번호
boolean visited[] = new boolean[n + 1]; // 방문 여부를 검사할 배열
LinkedList<Integer>[] adjList = new LinkedList[n + 1];
for (int i = 0; i <= n; i++) {
adjList[i] = new LinkedList<Integer>();
}
// 두 정점 사이에 여러 개의 간선이 있을 수 있다.
// 입력으로 주어지는 간선은 양방향이다.
for (int i = 0; i < m; i++) {
int v1 = sc.nextInt();
int v2 = sc.nextInt();
adjList[v1].add(v2);
adjList[v2].add(v1);
}
for (int i = 1; i <= n; i++) { // 방문 순서를 위해 오름차순 정렬
Collections.sort(adjList[i]);
}
System.out.println("DFS - 인접리스트");
dfs_list(v, adjList, c);
1-1. 재귀를 이용해 DFS 구현
처음 DFS 함수를 호출하게 되면 int v에 탐색을 시작할 정점의 번호가 들어있고, 이 시작 정점부터 DFS 탐색을 시작한다.
정점을 방문하면 visited[v]에 true 값을 넣어 방문했다고 표시한 뒤 정점을 출력한다.
현재 정점과 인접한 정점들을 찾기위해 인접 리스트에 Iterator를 이용해 접근해 인접 정점을 탐색한다.
정점 v의 인접리스트를 순회하며 아직 방문하지 않은 visited가 false인 정점을 찾는다.
인접한 정점 w를 찾는다면 w에서부터 다시 DFS를 한다.
더 이상 방문하지 않은 정점이 없을 때까지 탐색을 하며 DFS를 하는 것이다.
// DFS - 인접리스트 - 재귀로 구현
public static void dfs_list(int v, LinkedList<Integer>[] adjList, boolean[] visited) {
visited[v] = true; // 정점 방문 표시
System.out.print(v + " "); // 정점 출력
Iterator<Integer> iter = adjList[v].listIterator(); // 정점 인접리스트 순회
while (iter.hasNext()) {
int w = iter.next();
if (!visited[w]) // 방문하지 않은 정점이라면
dfs_list(w, adjList, visited); // 다시 DFS
}
}
int[][] adjArray = new int[n+1][n+1];
// 두 정점 사이에 여러 개의 간선이 있을 수 있다.
// 입력으로 주어지는 간선은 양방향이다.
for(int i = 0; i < m; i++) {
int v1 = sc.nextInt();
int v2 = sc.nextInt();
adjArray[v1][v2] = 1;
adjArray[v2][v1] = 1;
}
System.out.println("DFS - 인접행렬 / 재귀로 구현");
dfs_array_recursion(v, adjArray, visited);
Arrays.fill(visited, false); // 스택 DFS를 위해 visited 배열 초기화
System.out.println("\n\nDFS - 인접행렬 / 스택으로 구현");
dfs_array_stack(v, adjArray, visited, true);
2-1. 재귀를 이용해 DFS 구현
인접리스트에서 재귀를 이용해 구현했던 방법과 동일하다.
//DFS - 인접행렬 / 재귀로 구현
public static void dfs_array_recursion(int v, int[][] adjArray, boolean[] visited) {
int l = adjArray.length-1;
visited[v] = true;
System.out.print(v + " ");
for(int i = 1; i <= l; i++) {
if(adjArray[v][i] == 1 && !visited[i]) {
dfs_array_recursion(i, adjArray, visited);
}
}
}
2-2. 스택을 이용해 DFS 구현
처음 DFS 함수를 호출하게 되면 int v에 탐색을 시작할 정점의 번호가 들어있고, 이 시작 정점 부터 DFS 탐색을 시작한다.
스택을 생성하고 시작 정점 v의 값을 스택에 넣는다.
//DFS - 인접행렬 / 스택으로 구현
public static void dfs_array_stack(int v, int[][] adjArray, boolean[] visited, boolean flag) {
int l = adjArray.length-1;
Stack<Integer> stack = new Stack<Integer>();
stack.push(v);
visited[v] = true;
System.out.print(v + " ");
while(!stack.isEmpty()) {
int w = stack.peek();
flag = false;
for(int i = 1; i <= l; i++) {
if(adjArray[w][i] == 1 && !visited[i]) {
stack.push(i);
System.out.print(i + " ");
visited[i] = true;
flag = true;
break;
}
}
if(!flag) {
stack.pop();
}
}
}
전체 코드
1. 인접 리스트로 구현한 DFS
import java.util.*;
public class DFS_List {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 정점의 개수
int m = sc.nextInt(); // 간선의 개수
int v = sc.nextInt(); // 탐색을 시작할 정점의 번호
boolean visited[] = new boolean[n + 1]; // 방문 여부를 검사할 배열
LinkedList<Integer>[] adjList = new LinkedList[n + 1];
for (int i = 0; i <= n; i++) {
adjList[i] = new LinkedList<Integer>();
}
// 두 정점 사이에 여러 개의 간선이 있을 수 있다.
// 입력으로 주어지는 간선은 양방향이다.
for (int i = 0; i < m; i++) {
int v1 = sc.nextInt();
int v2 = sc.nextInt();
adjList[v1].add(v2);
adjList[v2].add(v1);
}
for (int i = 1; i <= n; i++) { // 방문 순서를 위해 오름차순 정렬
Collections.sort(adjList[i]);
}
System.out.println("DFS - 인접리스트");
dfs_list(v, adjList, visited);
}
// DFS - 인접리스트 - 재귀로 구현
public static void dfs_list(int v, LinkedList<Integer>[] adjList, boolean[] visited) {
visited[v] = true; // 정점 방문 표시
System.out.print(v + " "); // 정점 출력
Iterator<Integer> iter = adjList[v].listIterator(); // 정점 인접리스트 순회
while (iter.hasNext()) {
int w = iter.next();
if (!visited[w]) // 방문하지 않은 정점이라면
dfs_list(w, adjList, visited); // 다시 DFS
}
}
}
입력
5 5 3
5 4
5 2
1 2
3 4
3 1
출력
DFS - 인접리스트
3 1 2 5 4
import java.util.*;
public class DFS_Array {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 정점의 개수
int m = sc.nextInt(); // 간선의 개수
int v = sc.nextInt(); // 탐색을 시작할 정점의 번호
boolean visited[] = new boolean[n + 1]; // 방문 여부를 검사할 배열
int[][] adjArray = new int[n+1][n+1];
// 두 정점 사이에 여러 개의 간선이 있을 수 있다.
// 입력으로 주어지는 간선은 양방향이다.
for(int i = 0; i < m; i++) {
int v1 = sc.nextInt();
int v2 = sc.nextInt();
adjArray[v1][v2] = 1;
adjArray[v2][v1] = 1;
}
System.out.println("DFS - 인접행렬 / 재귀로 구현");
dfs_array_recursion(v, adjArray, visited);
Arrays.fill(visited, false); // 스택 DFS를 위해 visited 배열 초기화
System.out.println("\n\nDFS - 인접행렬 / 스택으로 구현");
dfs_array_stack(v, adjArray, visited, true);
}
//DFS - 인접행렬 / 재귀로 구현
public static void dfs_array_recursion(int v, int[][] adjArray, boolean[] visited) {
int l = adjArray.length-1;
visited[v] = true;
System.out.print(v + " ");
for(int i = 1; i <= l; i++) {
if(adjArray[v][i] == 1 && !visited[i]) {
dfs_array_recursion(i, adjArray, visited);
}
}
}
//DFS - 인접행렬 / 스택으로 구현
public static void dfs_array_stack(int v, int[][] adjArray, boolean[] visited, boolean flag) {
int l = adjArray.length-1;
Stack<Integer> stack = new Stack<Integer>();
stack.push(v);
visited[v] = true;
System.out.print(v + " ");
while(!stack.isEmpty()) {
int w = stack.peek();
flag = false;
for(int i = 1; i <= l; i++) {
if(adjArray[w][i] == 1 && !visited[i]) {
stack.push(i);
System.out.print(i + " ");
visited[i] = true;
flag = true;
break;
}
}
if(!flag) {
stack.pop();
}
}
}
}
입력
4 5 1
1 2
1 3
1 4
2 4
3 4
출력
DFS - 인접행렬 / 스택으로 구현
1 2 4 3
정점의 수가 n이고, 간선의 수가 e인 그래프의 경우
(희소 그래프는 그래프 내에 적은 수의 간선을 가지는 그래프로 인접 행렬을 사용하면 메모리의 낭비가 크기 때문)