KB IT's Your Life/알고리즘

[02] 재귀, 그래프(graph), 미로찾기

지식보부상님 2025. 5. 9. 09:36

✅ 실전 그래프 활용 - 인접 리스트 (list)

위의 그래프를 다음과 같이 인접 리스트(ArrayList)로 저장

List<List<Integer>> graph = new ArrayList<>() {{
    add(List.of(1, 3, 6));
    add(List.of(0, 3));
    add(List.of(3));
    add(List.of(0, 1, 2, 7));
    add(List.of(5));
    add(List.of(4, 6, 7));
    add(List.of(0, 5));
    add(List.of(3, 5));
}};

 

◈ 그래프의 간선(edge) 정보가 주어질 때 

(예시)

edges = [[0, 1], [0, 3], [0, 6], [1, 3], [2, 3], [3, 7], [4, 5], [5, 6], [5, 7]]
n = 8 # 노드의 개수

위처럼 그래프의 간선 정보가 주어지면 다음과 같이 인접 리스트를 저장하면 된다.

List<List<Integer>> graph = new ArrayList<>();

// 그래프 초기화
// n : vertex 개수
for(int i = 0 ; i < n ; i++) {
    graph.add(new ArrayList<>());
}

// 간선 정보 저장
for(int edge[] : edges){
    int x = edge[0];
    int y = edge[1];
    
    // 양방향 그래프
    graph.get(x).add(y); // x -> y 연결
    graph.get(y).add(x); // y -> x 연결
}

 

◈ 다음 노드 탐색

for(int nextVertex : graph.get(curVertex)){ ... }

실전 그래프 활용 - 인접 리스트 (dict)

 

위와 같은 그래프 주어졌을 때 해쉬맵(HashMap)으로 저장

Map<Integer, List<Integer>> graph = new HashMap<>() {{
    put(0, List.of(1, 3, 6));
    put(1, List.of(0, 3));
    put(2, List.of(3));
    put(3, List.of(0, 1, 2, 7));
    put(4, List.of(5));
    put(5, List.of(4, 6, 7));
    put(6, List.of(0, 5));
    put(7, List.of(3, 5));
}};

 

◈ 그래프의 간선(edge) 정보가 주어질 때 

(예시)

edges = [[0, 1], [0, 3], [0, 6], [1, 3], [2, 3], [3, 7], [4, 5], [5, 6], [5, 7]]
n = 8 # 노드의 개수

위처럼 그래프의 간선 정보가 주어지면 다음과 같이 인접 리스트를 저장하면 된다.

Map<Integer, List<Integer>> graph = new HashMap<>();

// 인접 리스트 초기화
for(int i = 0; i < n ; i++) {
    graph.put(i, new ArrayList<>());
}

// 간선 추가
for( int[] edge : edges) {
    int x = edge[0];
    int y = edge[1];
    
    // 양방향 그래프
    graph.get(x).add(y);  // x -> y
    graph.get(y).add(x);  // y -> x 연결
}

 

◈ 다음 노드 탐색

for(int nextVertex : graph.get(curVertex)){ ... }

 


✅ 실전 그래프 활용 - 인접 행렬

 

위와 같은 그래프 주어졌을 때 인접 행렬로 그래프 정보 저장

int[][] graph = {
    {0, 1, 0, 1, 0, 0, 1, 0},
    {1, 0, 0, 1, 0, 0, 0, 0},
    {0, 0, 0, 1, 0, 0, 0, 0},
    {1, 1, 1, 0, 0, 0, 0, 1},
    {0, 0, 0, 0, 0, 1, 0, 0},
    {0, 0, 0, 0, 1, 0, 1, 1},
    {1, 0, 0, 0, 0, 1, 0, 0},
    {0, 0, 0, 1, 0, 1, 0, 0}
};

 

◈ 인접 행렬을 인접 리스트로 변환

int[][] graph = {
    {0, 1, 0, 1, 0, 0, 1, 0},
    {1, 0, 0, 1, 0, 0, 0, 0},
    {0, 0, 0, 1, 0, 0, 0, 0},
    {1, 1, 1, 0, 0, 0, 0, 1},
    {0, 0, 0, 0, 0, 1, 0, 0},
    {0, 0, 0, 0, 1, 0, 1, 1},
    {1, 0, 0, 0, 0, 1, 0, 0},
    {0, 0, 0, 1, 0, 1, 0, 0}
};

// 인접 행렬 초기화
List<List<Integer>> adjList = new ArrayList<>();
int n = graph.length;

for(int i = 0 ; i < n ; i++ ){
    adjList.add(i, new ArrayList<>());
    for(int j = 0 ; j < n ; j++ ) {
        if(graph[i][j] == 1) {
            adjList.get(i).add(j); // i -> j 연결
        }
    }
}

✅ DFS 구현

 

◈ visited를 set으로 구현한 경우

public void dfs(List<List<Integer>> graph, Set<Integer> visited, int curVertex){
    visited.add(curVertex);
    
    for( int nextVertex : graph.get(curVertex)){
        if(!visited.contains(nextVertex){
            dfs(graph, visited, nextVertex);
        }
    }
}

public void solve(List<List<Integer>> graph){
    Set<Integer> visited = HashMap<>();
    dfs(graph, visited, 0);
}

 

◈ visited를 array로 구현한 경우

public void dfs(List<List<Integer>> graph, boolean[] visited, int curVertex){
    visited[curVertex] = true;
    
    for( int nextVertex : graph.get(curVertex){
        if(!visited[nextVertex]){
            dfs(graph, visited, nextVertex);
        }
    }
}

public void solution(List<List<Integer>> graph){
	final int N = graph.size();
    boolean[] visited = new boolean[N];
    dfs(graph, visited, 0);
}

✅ BFS 구현

◈ visited를 set으로 구현

public void bfs(List<List<Integer>> graph, int startVertex){
    Queue<Integer> queue = new LinkedList<>();
    Set<Integer> visited = new HashSet<>();
    
    queue.add(startVertex);
    visited.add(startVertex);
    
    while(!queue.isEmpty()){
        int curVertex = queue.remove();
        
        for( int nextVertex : graph.get(curVertex) {
            if(!visited.contains(nextVertex)){
                queue.add(nextVertex);
                visited.add(nextVertex);
            }
        }
    }    
}

public void solution(List<List<Integer>> graph) {
    bfs(graph, 0);
}

 

◈ visited를 array로 구현

public void bfs(List<List<Integer>> graph, int startVertex){
    Queue<Integer> queue = new LinkedList<>();
    final int N = graph.size();
    boolean[] visited = new boolean[N];
    
    queue.add(startVertex);
    visited[startVertex] = true;
    
    while(!queue.isEmpty()){
        int curVertex = queue.remove();
        for( int nextVertex : graph.get(curVertex)){
           if(!visited[nextVertex]){
               queue.add(nextVertex);
               visited[nextVertex] = true;
           }
        }
    }
}

public void solution(List<List<Integer>> graph){
    bfs(graph, 0);
}

✅ DFS & BFS 를 이용한 네트워크 개수 찾기

◈ 네트워크란?

- 서로 연결된 노드들의 집합

- 즉 연결된 덩어리

- dfs / bfs를 실행하면 하나의 네트워크를 탐색한 것

 

◈ 예제

// 인접 리스트로 그래프 표현
Map<Integer, List<Integer>> graph = new HashMap<>() {{
    put(0, List.of(1, 3));
    put(1, List.of(0, 3, 5));
    put(2, List.of(4));
    put(3, List.of(0, 1));
    put(4, List.of(2));
    put(5, List.of(1));
}};

위와 같이 저장된 graph는 다음과 같이 연결되어 있다

[네트워크 1]
   0
  / \
 1 - 3
 |
 5

[네트워크 2]
 2 - 4

 

네트워크의 개수를 찾는 코드

public int countNetworks(Map<Integer, List<Integer>> graph) {
    Set<Integer> visited = new ArrayList<>();
    int count = 0;
    
    for( int curVertex : graph.keySet()) {
        if(!visited.contains(curVertex)) {
            dfs(graph, visited, curVertex);
            count++;
        }
    }
    return count;
}

 


💻<예제>

1️⃣ 네트워크

더보기

✅ 문제 파악

  • 서로 연결된 네트워크의 개수 구하기
  • 매개변수
    • computers : 인접 행렬로 주어지는 그래프
    • n : 컴퓨터의 개수

 접근 방법

  • 네트워크의 개수는 bfs / dfs 로 그래프를 탐색하여 구할 수 있다.

 코드 구현

import java.util.*;

class Solution {
    public int solution(int n, int[][] computers) {
        int answer = 0;
        final int N = computers.length;
        Map<Integer, List<Integer>> adjList = new HashMap<>();
        Set<Integer> visited = new HashSet<>();
        
        // 인접 행렬을 인접 리스트로 변환
        for(int i = 0 ; i < N ; i++) {
            adjList.put(i, new ArrayList<>());
            for(int j = 0 ; j < N ; j++) {
                if(computers[i][j] == 1 && i != j) {
                    adjList.get(i).add(j);
                }
            }
        }
        
        // 네트워크 찾기
        for( int curVertex : adjList.keySet()) {
            if(!visited.contains(curVertex)){
                bfs(adjList, visited, curVertex);
                answer++;
            }
        }
        return answer;
    }
    
    public void bfs(Map<Integer, List<Integer>> graph, Set<Integer> visited, int curVertex) {
        visited.add(curVertex);
        for (int nextVertex : graph.get(curVertex)) {
            if (!visited.contains(nextVertex)) {
                bfs(graph, visited, nextVertex);
            }
        }
    }
}

 

2️⃣ Keys and Rooms

더보기

✅ 문제 파악

  • 리턴
    • 전체 방을 모두 방문할 수 있으면 true, 아니면 false 리턴
  • 변수
    • roooms : rooms[i] 에는 i번 방 이후에 방문할 수 있는 방이 저장되어 있음

✅ 접근 방법

  • 방을 vertex, 주어진 키를 edge 라고 생각하여 그래프로 해석
  • 모두 방문 가능한지는 dfs / bfs 를 이용하여 확인 가능

✅ 코드 구현

import java.util.*;

class Solution {
    public boolean canVisitAllRooms(List<List<Integer>> rooms) {
        final int N = rooms.size();
        boolean[] visited = new boolean[N];
        
        dfs(rooms, visited, 0);

        for(boolean room : visited) {
            if(!room)
                return false;
        }
        
        return true;
    }

    public void dfs(List<List<Integer>> rooms, boolean[] visited, int curRoom) {
       visited[curRoom] = true;

        for(int nextRoom : rooms.get(curRoom)) {
            if(!visited[nextRoom]) {
                dfs(rooms, visited, nextRoom);
            }
        }
    }
}

 

3️⃣ 가장 먼 노드

더보기

✅ 문제 파악

  • 1~n 까지의 노드
  • 리턴
    • leaf 노드 중 가장 먼 노드의 개수 구하기
  • 변수
    • n : 노드의 개수
    • edge : edge[i] 에는 두 노드 [x, y] 가 저장되어 있음

✅ 접근 방법

  • bfs 이용하여 각 노드의 depth 를 visited의 key 로 저장
  • 주의❗ edge에는 1~n 까지의 노드가 저장되어있으므로, 자체적으로 0~n-1 번의 노드로 바꾸어 저장

✅ 코드 구현

import java.util.*;

class Solution {
    public int solution(int n, int[][] edge) {
        int answer = 0;
        // 그래프 초기화
        List<List<Integer>> graph = new ArrayList<>();
        
        for(int i = 0 ; i < n ; i++) {
            graph.add(new ArrayList<>());
        }
        
        // 인접 리스트로 저장
        for(int[] e : edge) {
            int x = e[0]-1;
            int y = e[1]-1;
            
            graph.get(x).add(y);
            graph.get(y).add(x);
        }
        
        // bfs로 그래프 탐색
        answer = bfs(graph, 0);
        
        return answer;
    }
    
    public int bfs(List<List<Integer>> graph, int startNode) {
        Queue<Integer> queue = new LinkedList<>();
        
        // visited의 key: Node 번호, value: 그래프의 depth
        Map<Integer, Integer> visited = new HashMap<>();
        
        queue.add(startNode);
        visited.put(startNode, 1);
        
        while(!queue.isEmpty()) {
            int curNode = queue.remove();
            
            for(int nextNode : graph.get(curNode)) {
                if(!visited.containsKey(nextNode)) {
                    queue.add(nextNode);
                    visited.put(nextNode, visited.get(curNode)+1);
                }
            }
        }
        
        // depth 가 최대인 것의 개수 구하기
        int maxValue = 0; 
        int count = 0;
        for(int key : visited.keySet()){
            if(maxValue < visited.get(key)) {
                maxValue = visited.get(key);
                count = 1;
            } else if(maxValue == visited.get(key)) {
                count++;
            }
        }
        return count;
    }
}

✅ 배우게 된 점 (피드백)

  • visited에 각 노드의 depth 를 저장할 필요 없이 bfs 탐색시에 maxValue 와 count 를 세면 시간을 줄일 수 있다.

 

4️⃣ Coin Change

더보기

✅ 문제 파악

  • 변수
    • coins : 가진 동전의 종류
    • amount : 도달해야 할 값
  • 리턴
    • amount 에 도달가능하면 만들 수 있는 동전의 최소의 개수를 리턴
    • 도달 불가능하면 -1 을 리턴

✅ 접근 방법

  • 첫 접근
    • while문으로 큰 금액 동전부터 사용하도록 했음
    • ⇒ ❌ 큰 금액의 동전을 사용하지 않고도 amount 에 도달가능한 경우가 발생
  • dfs 이용
    • 0 ~ amount 가 그래프의 노드라고 생각
    • visited[i] : i 원에 도달했음을 의미
    • 큐에는 현재 남은 금액과 낸 동전의 개수를 저장

✅ 코드 구현

import java.util.*;

class Solution {
    public int coinChange(int[] coins, int amount) {
        int count = -1;

        if(amount == 0) {
            return 0;
        }

        Queue<int[]> queue = new LinkedList<>();
        boolean[] visited = new boolean[amount+1];

        visited[amount] = true;
        queue.add(new int[]{amount, 0});

        while(!queue.isEmpty()) {
            int[] cur = queue.remove();
            int curRemaining = cur[0];
            int coinCount = cur[1];

            if(curRemaining == 0) {
                return coinCount;
            }

            for(int coin : coins) {
                int next = curRemaining-coin;
                if(next >=0 && !visited[next]){
                    queue.add(new int[]{next, coinCount+1});
                    visited[next] = true;
                }
            }
        } 

    
        return count;
    }
}

 

5️⃣ Is Graph Bipartite

더보기

✅ 문제 파악

  • 변수
    • graph : graph[i] 에는 노드 i 와 연결된 노드가 저장되어있음
  • 리턴
    • bipartite이면 true, 아니면 false를 리턴

✅ 접근 방법

  • 노드 x 에 대해 연결된 노드 y 는 서로 다른 집합으로 분류
    • visited[i] 가 1 이거나 -1 인 집합으로 분류
    • visited[x] = -visited[y]
  • 노드 x 와 y 가 연결되어 있는데, visited 집합이 동일한 집합으로 분류되면 false 리턴

✅ 코드 구현

import java.util.*;

class Solution {
    public boolean isBipartite(int[][] graph) {
        Queue<Integer> queue = new LinkedList<>();
        final int n = graph.length;
        int[] visited = new int[n];

        for(int i = 0 ; i < n ; i++ ) {            
            if(visited[i] == 0) {
                queue.add(i);
                visited[i] = 1;
                while(!queue.isEmpty()) {
                    int cur = queue.remove();
                    for(int next : graph[cur]) {
                        if(visited[next] == 0) {
                            visited[next] = -visited[cur];
                            queue.add(next);
                        } else if(visited[next] == visited[cur]){
                            return false;
                        }
                    }
                }
            }
        }

        return true;
    }
}

 

6️⃣ 단어 변환