Java/알고리즘
[알고리즘/DFS, BFS] 깊이 우선 탐색, 넓이 우선 탐색 (스택, 재귀함수, 큐, 방문 여부 배열, 그래프 연결 정보 저장 2차원 리스트)
Se On
2025. 1. 20. 14:59
🖥️ 들어가며
📌DFS
: Depth First Search, 깊이 우선 탐색
📌BFS
: Breadth First Search, 넓이 우선 탐색
✏️ 스택 구현 예제
- Stack<Integer> s = new Stack<>();
- 삽입: push()
삭제: pop()
최상단 원소부터 출력: peek()
import java.util.*;
public class Main {
public static void main(String[] args) {
Stack<Integer> s = new Stack<>();
// 삽입: push, 삭제: pop
s.push(5);
s.push(2);
s.push(3);
s.push(7);
s.pop();
s.push(1);
s.push(4);
s.pop();
// 최상단 원소부터 출력: peek()
while (!s.isEmpty()) {
System.out.println(s.peek() + " ");
s.pop();
}
}
}
✏️ 큐 구현 예제
- Queue<Integer> q = new LinkedList<>();
큐 중에서도 연결리스트 방식으로 구현된 큐를 사용합니다. - 삽입: offer()
삭제: poll()*
*poll() 메서드: 원소를 꺼내고 반환해 줌
import java.util.*;
public class Main {
public static void main(String[] args) {
Queue<Integer> q = new LinkedList<>();
// 삽입: offer, 삭제: poll
q.offer(5);
q.offer(2);
q.offer(3);
q.offer(7);
q.poll();
q.offer(1);
q.offer(4);
q.poll();
// 먼저 들어온 원소부터 추출
while (!q.isEmpty()) {
System.out.println(q.poll() + " ");
}
}
}
✏️ 재귀함수
- 일반적인 코딩테스트에서 재귀함수를 사용할 때는 종료조건을 반드시 명시해야 합니다. (의도적으로 무한 루프를 원하는 게 아니라면)
- 종료조건을 제대로 명시하지 않으면 함수가 무한히 호출될 수 있습니다.
- 모든 재귀함수는 반복문을 이용하여 동일한 기능을 구현할 수 있습니다.
- 팩토리얼 구현
- 최대공약수 계산(유클리드 호제법)
👉🏻 유클리드 호제법
- 두 개의 자연수에 대한 최대공약수를 구하는 알고리즘입니다.
- 두 자연수 A, B에 대하여 (A > B) A를 B로 나눈 나머지 = R
- 이때 A와 B의 최대공약수는 B와 R의 최대공약수와 같습니다.
- 유클리드 호제법의 아이디어를 그대로 재귀 함수로 작성할 수 있습니다.
✏️ DFS, BFS 구현 방법
1. DFS
- 동작 과정
1. 탐색 시작 노드를 스택에 삽입하고 방문 처리합니다.
2. 스택의 최상단 노드에 방문하지 않은 인접한 노드가 하나라도 있으면 그 노드를 스택에 넣고 방문 처리합니다.
방문하지 않은 노드가 없으면 스택에서 최상단 노드를 꺼냅니다.
*방문기준: 문제 내용에 따라 상이
3. 더 이상 2번의 과정을 수행할 수 없을 때까지 반복합니다. - 응용
- 경로 찾기
- 위상 정렬
- 연결 요소 찾기
- 사이클 탐지
📌DFS code
- 전역 변수
- visited: 각 노드의 방문 여부를 저장하는 배열입니다.
- boolean[] visited = new boolean[9]: 크기가 9인 이유는 노드가 1~8까지 있기 때문입니다.
(0번 인덱스는 사용하지 않음)
- boolean[] visited = new boolean[9]: 크기가 9인 이유는 노드가 1~8까지 있기 때문입니다.
- graph: 그래프의 연결 관계를 저장하는 2차원 ArrayList입니다. 각 노드에 연결된 노드들의 리스트를 저장합니다.
- ArrayList<ArrayList<Integer>> graph = new ArrayList<ArrayList<Integer>>(); : ArrayList를 2차원 형태로 중첩해서 사용합니다.
- visited: 각 노드의 방문 여부를 저장하는 배열입니다.
- dfs 함수
- 재귀적으로 dfs를 수행합니다.
1. 현재 노드 x를 방문 처리합니다.
2. 현재 노드를 출력합니다.
3. 현재 노드와 연결된 모든 노드에 대해 아직 방문하지 않은 노드라면 해당 노드로 dfs를 재귀적으로 호출합니다. - for (int i = 0; i < graph.get(x).size(); i++): graph.get(x)는 x 노드에 연결된 다른 노드들의 리스트입니다. 이 리스트의 크기만큼 반복문을 돌며 연결된 노드를 탐색합니다.
- int y = graph.get(x).get(i): x 노드와 연결된 i번째 노드를 가져옵니다.
- if (!visited[y]) dfs(y): y 노드가 아직 방문되지 않았다면 dfs(y)를 호출하여 그 노드를 재귀적으로 탐색합니다.
- 재귀적으로 dfs를 수행합니다.
- main 함수
- graph.add(new ArrayList<Integer>(): 그래프를 나타내는 graph 리스트에 9개의 빈 ArrayList<Integer> 객체를 추가합니다. 이 리스트는 각 노드에 연결된 다른 노드들의 목록을 저장하는데 사용됩니다.
- graph.get(1).add(2): 노드 1에 연결된 노드 2를 그래프에 추가합니다.
- dfs(1): 깊이 우선 탐색을 시작합니다. (1번 노드에서부터 탐색 시작)
import java.util.*;
public class Main {
public static boolean[] visited = new boolean[9];
public static ArrayList<ArrayList<Integer>> graph = new ArrayList<ArrayList<Integer>>();
// DFS 함수 정의
public static void dfs(int x) {
// 현재 노드를 방문 처리
visited[x] = true;
System.out.print(x + " ");
// 현재 노드와 연결된 다른 노드를 재귀적으로 방문
for (int i = 0; i < graph.get(x).size(); i++) {
int y = graph.get(x).get(i);
if (!visited[y]) dfs(y);
}
}
public static void main(String[] args) {
// 그래프 초기화
for (int i = 0; i < 9; i++) {
graph.add(new ArrayList<Integer>());
}
// 노드 1에 연결된 노드 정보 저장
graph.get(1).add(2);
graph.get(1).add(3);
graph.get(1).add(8);
// 노드 2에 연결된 노드 정보 저장
graph.get(2).add(1);
graph.get(2).add(7);
// 노드 3에 연결된 노드 정보 저장
graph.get(3).add(1);
graph.get(3).add(4);
graph.get(3).add(5);
// 노드 4에 연결된 노드 정보 저장
graph.get(4).add(3);
graph.get(4).add(5);
// 노드 5에 연결된 노드 정보 저장
graph.get(5).add(3);
graph.get(5).add(4);
// 노드 6에 연결된 노드 정보 저장
graph.get(6).add(7);
// 노드 7에 연결된 노드 정보 저장
graph.get(7).add(2);
graph.get(7).add(6);
graph.get(7).add(8);
// 노드 8에 연결된 노드 정보 저장
graph.get(8).add(1);
graph.get(8).add(7);
dfs(1);
}
}
2. BFS
- BFS: Queue
- 특징
- 시작 노드에서 가까운 노드부터 탐색합니다.
- 큐 자료구조를 사용하여 구현합니다.
- 최단 경로를 찾는 데 유용합니다.
- 레벨 순서로 노드를 탐색합니다.
- 동작 과정
1. 시작 노드를 큐에 삽입하고 방문 처리합니다.
2. 큐에서 노드를 꺼낸 뒤 해당 노드의 인접 노드 중에서 방문하지 않은 노드를 모두 큐에 삽입하고 방문 처리합니다.
3. 더 이상 2번의 과정을 수행할 수 없을 때까지 반복합니다. - 응용
- 최단 경로 찾기
- 네트워크 탐색
- 웹 크롤링
- 미로 찾기
- 그래프의 연결 요소 찾기
📌BFS code
- get(): ArrayList에서 특정 값을 가져올 때 사용합니다.
- 전역 변수
- visited: 각 노드의 방문 여부를 저장하는 배열입니다.
- boolean[] visited = new boolean[9]: 크기가 9인 이유는 노드가 1~8까지 있기 때문입니다.
(0번 인덱스는 사용하지 않음)
- boolean[] visited = new boolean[9]: 크기가 9인 이유는 노드가 1~8까지 있기 때문입니다.
- graph: 그래프의 연결 관계를 저장하는 2차원 ArrayList입니다. 각 노드에 연결된 노드들의 리스트를 저장합니다.
- ArrayList<ArrayList<Integer>> graph = new ArrayList<ArrayList<Integer>>(); : ArrayList를 2차원 형태로 중첩해서 사용합니다.
- visited: 각 노드의 방문 여부를 저장하는 배열입니다.
- bfs 함수
- Queue<Integer> q = new LinkedList<>(): 큐를 사용하여 노드를 탐색합니다.
- q.offer(start): 탐색을 시작할 노드 start를 큐에 추가합니다.
- visited[start] = true: 현재 노드를 방문 처리합니다.
- while(!q.isEmpty()) {...}: 큐가 비어있지 않은 동안 반복문을 실행합니다.
- int x = q.poll(): 큐에서 원소를 하나 꺼냅니다.
- for (int i = 0; i < graph.get(x).size(); i++) {...}: x 노드와 연결된 다른 노드들의 리스트를 순회하면서 x와 연결된 노드를 탐색합니다.
- int y = graph.get(x).get(i): x 노드와 연결된 i번째 노드를 y 변수에 저장합니다.
- if (!visited[y]) {...}: y 노드가 아직 방문되지 않았다면, y 노드를 큐에 추가하고 방문 처리합니다.
- main 함수
- graph.add(new ArrayList<Integer>(): 그래프를 나타내는 graph 리스트에 9개의 빈 ArrayList<Integer> 객체를 추가합니다. 이 리스트는 각 노드에 연결된 다른 노드들의 목록을 저장하는데 사용됩니다.
- graph.get(1).add(2): 노드 1에 연결된 노드 2를 그래프에 추가합니다.
- bfs(1): 넓이 우선 탐색을 시작합니다. (1번 노드에서부터 탐색 시작)
import java.util.*;
public class Main {
public static boolean[] visited = new boolean[9];
public static ArrayList<ArrayList<Integer>> graph = new ArrayList<ArrayList<Integer>>();
// BFS 함수 정의
public static void bfs(int start) {
Queue<Integer> q = new LinkedList<>();
q.offer(start);
// 현재 노드를 방문 처리
visited[start] = true;
// 큐가 빌 때까지 반복
while(!q.isEmpty()) {
// 큐에서 하나의 원소를 뽑아 출력
int x = q.poll();
System.out.print(x + " ");
// 해당 원소와 연결된, 아직 방문하지 않은 원소들을 큐에 삽입
for(int i = 0; i < graph.get(x).size(); i++) {
int y = graph.get(x).get(i);
if(!visited[y]) {
q.offer(y);
visited[y] = true;
}
}
}
}
public static void main(String[] args) {
// 그래프 초기화
for (int i = 0; i < 9; i++) {
graph.add(new ArrayList<Integer>());
}
// 노드 1에 연결된 노드 정보 저장
graph.get(1).add(2);
graph.get(1).add(3);
graph.get(1).add(8);
// 노드 2에 연결된 노드 정보 저장
graph.get(2).add(1);
graph.get(2).add(7);
// 노드 3에 연결된 노드 정보 저장
graph.get(3).add(1);
graph.get(3).add(4);
graph.get(3).add(5);
// 노드 4에 연결된 노드 정보 저장
graph.get(4).add(3);
graph.get(4).add(5);
// 노드 5에 연결된 노드 정보 저장
graph.get(5).add(3);
graph.get(5).add(4);
// 노드 6에 연결된 노드 정보 저장
graph.get(6).add(7);
// 노드 7에 연결된 노드 정보 저장
graph.get(7).add(2);
graph.get(7).add(6);
graph.get(7).add(8);
// 노드 8에 연결된 노드 정보 저장
graph.get(8).add(1);
graph.get(8).add(7);
bfs(1);
}
}
📒 정리하면
- DFS: 깊이 우선 탐색, 스택 또는 재귀를 사용합니다. 경로 탐색, 위상 정렬 등에 사용됩니다.
(방문 여부 배열, 그래프 연결 정보 2차원 리스트, 시작노드 → 인접노드를 재귀적으로 탐색합니다.) - BFS: 넓이 우선 탐색, 큐를 사용합니다. 최단 경로 찾기에 유용합니다.
(방문 여부 배열, 그래프 연결 정보 2차원 리스트, 시작노드 → 인접노드를 큐에서 순차적으로 방문합니다.) - 스택(Stack): 삽입 push(), 삭제 pop(), 최상위 요소 확인 peek()
큐(Queue): 삽입 offer(), 삭제 poll() - 값을 반환하면서 삭제합니다. - 재귀함수: (무한루프를 원하는 게 아니라면) 종료 조건을 명시해야 하며, 반복문으로 대체할 수 있습니다.
(ex: 팩토리얼 계산, 유클리드 호제법(최대공약수 계산)) - ⭐⭐⭐ 방문 여부 배열(0번 인덱스는 사용하지 않으므로 +1 사이즈로),
⭐⭐⭐ 그래프 연결 정보 저장 2차원 리스트(ArrayList<ArrayList<Integer>> graph)
반응형