グラフアルゴリズムパターン

中級

カテゴリ: graphs

概要

グラフアルゴリズムは、頂点(ノード)と辺(エッジ)からなるグラフ構造上の問題を解くための手法です。グラフは多くの実世界の問題(ネットワーク、関係性、経路など)をモデル化するのに適しており、グラフアルゴリズムはそれらの問題を効率的に解決するための基盤となります。主要なグラフアルゴリズムには、グラフ走査(DFS、BFS)、最短経路探索(ダイクストラ法、ベルマンフォード法)、最小全域木(クラスカル法、プリム法)、強連結成分、トポロジカルソートなどがあります。

コードテンプレート

Java
Python
- グラフの表現(隣接リスト)
class Graph {
    private int V; // 頂点数
    private List<List<Integer>> adj; // 隣接リスト
    
    public Graph(int v) {
        V = v;
        adj = new ArrayList<>(v);
        for (int i = 0; i < v; i++) {
            adj.add(new ArrayList<>());
        }
    }
    
    // 辺を追加(無向グラフの場合)
    public void addEdge(int v, int w) {
        adj.get(v).add(w);
        adj.get(w).add(v); // 有向グラフの場合はこの行を削除
    }
    
    // 深さ優先探索(DFS)
    public void dfs(int start) {
        boolean[] visited = new boolean[V];
        dfsUtil(start, visited);
    }
    
    private void dfsUtil(int v, boolean[] visited) {
        // 現在のノードを訪問済みとしてマーク
        visited[v] = true;
        System.out.print(v + " ");
        
        // 隣接するすべての頂点を再帰的に探索
        for (Integer neighbor : adj.get(v)) {
            if (!visited[neighbor]) {
                dfsUtil(neighbor, visited);
            }
        }
    }
    
    // 幅優先探索(BFS)
    public void bfs(int start) {
        boolean[] visited = new boolean[V];
        Queue<Integer> queue = new LinkedList<>();
        
        // 開始ノードを訪問済みとしてマークし、キューに追加
        visited[start] = true;
        queue.add(start);
        
        while (!queue.isEmpty()) {
            // キューから頂点を取り出し、表示
            int v = queue.poll();
            System.out.print(v + " ");
            
            // 隣接するすべての未訪問の頂点をキューに追加
            for (Integer neighbor : adj.get(v)) {
                if (!visited[neighbor]) {
                    visited[neighbor] = true;
                    queue.add(neighbor);
                }
            }
        }
    }
    
    // ダイクストラ法(単一始点最短経路)
    public int[] dijkstra(int start) {
        int[] dist = new int[V]; // 最短距離を格納する配列
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[start] = 0;
        
        // 訪問済みの頂点を追跡
        boolean[] visited = new boolean[V];
        
        // 全頂点について処理
        for (int count = 0; count < V - 1; count++) {
            // 未訪問の頂点の中から最短距離の頂点を選択
            int u = minDistance(dist, visited);
            
            // 選択した頂点を訪問済みとしてマーク
            visited[u] = true;
            
            // 選択した頂点の隣接頂点の距離を更新
            for (int v = 0; v < V; v++) {
                // 未訪問で、辺が存在し、パスが存在し、新しいパスがより短い場合
                if (!visited[v] && adj.get(u).contains(v) && 
                    dist[u] != Integer.MAX_VALUE && 
                    dist[u] + 1 < dist[v]) { // ここでは重みを1と仮定
                    dist[v] = dist[u] + 1;
                }
            }
        }
        
        return dist;
    }
    
    // 未訪問の頂点の中から最短距離の頂点を見つける補助メソッド
    private int minDistance(int[] dist, boolean[] visited) {
        int min = Integer.MAX_VALUE;
        int minIndex = -1;
        
        for (int v = 0; v < V; v++) {
            if (!visited[v] && dist[v] <= min) {
                min = dist[v];
                minIndex = v;
            }
        }
        
        return minIndex;
    }
}
Java

ひらめきのサポート

問題文に以下のようなキーワードや特徴がある場合、このパターンの適用を検討してみましょう。

グラフ頂点隣接リスト隣接行列DFSBFSダイクストラ法ベルマンフォード法フロイドワーシャル法最短経路最小全域木トポロジカルソート連結成分サイクル検出

チャート式ひらめきポイント

問題を読んだときに、「ソート済み配列で何かを見つける」「二つの要素の関係を調べる」「効率的に配列を操作する」などの特徴があれば、 双指針パターンが適用できる可能性が高いです。特に、O(n²)の計算量を避けたい場合に有効です。

このパターンを使用する問題例

  • 1

    島の数(Number of Islands): グリッド上の連結成分の数を数える

  • 2

    コースのスケジュール(Course Schedule): 有向グラフのサイクル検出とトポロジカルソート

  • 3

    ネットワークの遅延時間(Network Delay Time): 単一始点最短経路問題

  • 4

    冗長接続(Redundant Connection): グラフのサイクルを作る辺を見つける

  • 5

    最小全域木(Minimum Spanning Tree): グラフのすべての頂点を最小コストで接続する

関連するパターン

dfsbfstopological-sortdijkstra-algorithmunion-find