2025.04.10 ~ 04.11
순차 탐색(Linear Search)
이진 탐색(Binary Search)
DFS(깊이 탐색)
BFS(너비 탐색)
-> Stack, Queue를 이용, 재귀호출도 연관
방문배열
간선
DFS(깊이 탐색), BFS(너비 탐색)을 사용하는 문제
후입선출(LIFO : last in, first out) 구조인 stack, 재귀함수를 활용해 한쪽 분기를 정하여 최대 깊이까지 탐색을 맞친 후, 다른 쪽 분기로 이동하여 탐색을 수행하는 알고리즘




static int node, edge; <!-- 노드, 간선 -->
static int[][] map; <!-- 인접 행렬로 그래프를 나타내기 위함 -->
static boolean[] visit; <!-- 노드를 방문했을 때 TRUE를 넣어서 다시 방문하지 않도록 함 -->
<!-- 그래프의 간선 연결 정보를 이차원 배열로 표현 -->
map = new int[node + 1][node + 1]; <!-- 0번 인덱스 제외하고 사용 -->
<!-- 방문 배열 생성 (지나간 노드를 다시 방문하지 않기 위함) -->
visit = new boolean[node + 1];
<!-- 무방향 그래프 간선 연결 둘 다 1로 설정 -->
map[a][b] = map[b][a] = 1;
private static void dfsRecursive(int start) {
<!-- 해당 노드를 방문했으므로 방문 배열에 표기 -->
visit[start] = true;
<!-- start 노드의 이웃을 탐색하는 과정 -->
for(int i = 1; i <= node; i++) {
<!-- start 정점의 이웃 중 방문하지 않은 이웃을 찾는다. -->
if(map[start][i] == 1 && !visit[i]) {
<!-- 연결된 이웃 노드를 찾은 것이므로 count를 증가시키고
해당 이웃 노드를 방문해서 다시 이웃노드를 재귀적으로 탐색한다. -->
count++;
dfsRecursive(i);
}
}
}
private static void dfsStack(int start) {
Stack<Integer> stack = new Stack<>();
stack.push(start);
visit[start] = true;
while(!stack.isEmpty()) {
int current = stack.pop();
for(int i = 1; i <= node; i++) {
// 그래프의 노드가 있을 때 방문하지 않은 경우
if(map[current][i] == 1 && !visit[i]) {
stack.push(i);
visit[i] = true;
count++;
}
}
}
}
재귀함수 사용시점
stack 사용 시점
선입선출(FIFO : first in, first out) 구조인 queue를 활용해 탐색 시작 노드와 가까운 노드를 우선하여 탐색하는 알고리즘



static int M, N, K; <!-- 가로, 세로, 개수 -->
static int[][] map; <!-- 인접 행렬로 그래프를 나타내기 위함 -->
static boolean[] visit; <!-- 노드를 방문했을 때 TRUE를 넣어서 다시 방문하지 않도록 함 -->
<!-- 상하좌우, 같은 인덱스의 X,Y를 한 쌍으로 본다. -->
static int[] dirX = {0, 0, -1, 1}; // x축(열) 이동
static int[] dirY = {-1, 1, 0, 0}; // y축(행) 이동
<!-- x(열)와 y(행) 좌표를 가지는 Node 클래스 -->
<!-- 한 지점을 큐에 넣을 때, 그 지점의 한 쌍의 좌표를 함께 기억하기 위한 노드 -->
<!-- java는 좌표를 저장할 수 있는 node 클래스가 없기 때문에 따로 지정 -->
static class Node {
int x;
int y;
public Node(int x, int y) {
this.x = x;
this.y = y;
}
}
<!-- 현재 좌표 -->
static int cx, cy;
static Queue<Node> queue = new LinkedList<>();
<!-- 좌표 범위 확인 -->
static boolean rangeCheck() {
return cx >= 0 && cy >= 0 && cx < M && cy < N;
}
<!-- 행, 열의크기를 바꾸지 않는다 -->
map = new int[M][N];
visit = new boolean[M][N];
bfs(i, j);
static void bfs(int x, int y) {
<!-- 해당 좌표를 방문한 좌표에 삽입 -->
visit[x][y] = true;
<!-- 큐에 좌표 한 쌍으로 삽입 -->
queue.offer(new Node(x, y));
<!-- 큐의 저장해 놓았던 것을 꺼내와서 탐색을 한다. -->
while(!q.isEmpty()) {
Node node = q.poll();
<!-- 상하좌우 살펴보기 -->
for(int i = 0; i < 4; i++) {
cx = node.x + dirX[i];
cy = node.y + dirY[i];
<!-- 가장 모서리 혹은 끝에 있는 요소들은 상하좌우를 판단하려고 할 때
없는 공간을 참조할 수 있게 된다.
IndexOutOfBound Exception 이 발생하기 때문에
외부에 Error 방지용 메서드 선언 -->
<!-- 범위 확인 && 중복 방문 확인(아직 방문하지 않을 경우 실행)
&& 해당 좌표가 실제로 존재하는지 확인(존재하면 탐색 큐에 다시 넣고 탐색) -->
if(rangeCheck() && !visit[cx][cy] && map[cx][cy] == 1) {
queue.offer(new Node(cx, cy));
visit[cx][cy] = true;
}
}
}
}
<!-- 행, 열의크기를 바꾼다. -->
map = new int[N][M];
visit = new boolean[N][M];
bfs(j, i);
static void bfs(int x, int y) {
<!-- 해당 좌표를 방문한 좌표에 삽입 -->
visit[y][x] = true;
<!-- 큐에 좌표 한 쌍으로 삽입 -->
queue.offer(new Node(x, y));
<!-- 큐의 저장해 놓았던 것을 꺼내와서 탐색을 한다. -->
while(!queue.isEmpty()) {
Node node = queue.poll();
<!-- 상하좌우 살펴보기 -->
for(int i = 0; i < 4; i++) {
cx = node.x + dirX[i];
cy = node.y + dirY[i];
<!-- 범위 확인 && 중복 방문 확인(아직 방문하지 않을 경우 실행)
&& 해당 좌표가 실제로 존재하는지 확인(존재하면 탐색 큐에 다시 넣고 탐색) -->
if(rangeCheck() && !visit[cy][cx] && map[cy][cx] == 1) {
queue.offer(new Node(cx, cy));
visit[cy][cx] = true;
}
}
}
}
먼저 방문한 노드를 기준으로 가장 가까운 노드부터 탐색
static int N, M;
static int[][] map;
static boolean[][] visit;
static int[] dirX = {0, 0, -1, 1};
static int[] dirY = {-1, 1, 0, 0};
static class Node {
int x;
int y;
public Node(int x, int y) {
this.x = x;
this.y = y;
}
}
static Queue<Node> queue = new LinkedList<>();
<!-- 출발 지점 -->
bfs(0, 0);
static void bfs(int x, int y) {
queue.add(new Node(x, y));
visit[x][y] = true;
<!-- 시작 지점에서부터 큐를 이용해서 큐에 담기는 노드가 제거 될 때까지 너비 우선 탐색을 진행 -->
while(!queue.isEmpty()) {
<!-- 현재 탐색을 진행할 노드 -->
Node node = queue.poll();
<!-- 해당 정점에서 상하좌우를 살펴보는 반복문 -->
for(int i = 0; i < 4; i++) {
int cx = node.x + dirX[i];
int cy = node.y + dirY[i];
<!-- 좌표가 전체 범위를 넘어간다면 확인이 불필요하므로 다음 방향 확인 -->
if(cx < 0 || cy < 0 || cx >= N || cy >= M) continue;
<!-- 방문 했던 좌표이거나 갈 수 없는 곳이라면 확인이 불필요하므로 다음 방향 확인 -->
if(visit[cx][cy] || map[cx][cy] == 0) continue;
<!-- 유효한 좌표이면 탐색 대상 큐에 추가 -->
<!-- 현재 좌표까지의 거리 +1을 저장 (이동 거리 누적) -->
queue.offer(new Node(cx, cy));
visit[cx][cy] = true;
map[cx][cy] = map[node.x][node.y] + 1;
}
}
}
좌표 f(x, y)
인덱스[row][col]
좌표와 인덱스 차이를 신경써야되는 문제
입력 받는 문자열이 공백 없이 한 줄로 붙어 있는 경우
for(int i = 0; i < N; i++) {
<!-- .toCharArray() : 한 줄씩 읽어서 문자 배열로 변환 -->
char[] ch = br.readLine().toCharArray();
for(int j = 0; j < ch.length; j++) {
<!-- .getNumericValue() : 문자를 정수로 변환 -->
map[i][j] = Character.getNumericValue(ch[j]); // '1' -> 숫자 1
}
}
아스키 코드 값을 이용해 알파벳에서 'A','a'을 빼준다.
char parent = st.nextToken().charAt(0);
int idx = parent - 'A';
그래프 탐색 문제는 노드 시작번호를 기준으로 코드를 작성하기 때문에 인덱스와 좌표를 맞춰주는 것이 좋다.
격자형(2차원 좌표계)는 인덱스 자체가 (x, y)좌표 역할을 한다. 좌표가 0부터 시작하므로 별도로 인덱스를 맞출 필요가 없다.