Keito

© 2024 Keito

技術ブログとポートフォリオ

深さ優先探索(DFS)

グラフを深く探索するアルゴリズム。バックトラッキングで全経路を系統的に調査

O(V+E)
時間計算量
O(V)
空間計算量
中級
難易度
グラフ探索
手法

🌲 実行設定

実装方式

ノード数:6
辺数:6
開始ノード:A
ターゲット:なし(全探索)

📊 推奨グラフ

🌲

深さ優先探索を実行してください

左側の設定パネルからグラフを設定し、「DFS実行」ボタンを押してください

💻 実装例(JavaScript)

// 再帰による深さ優先探索
function dfsRecursive(graph, startNode, visited = new Set()) {
    // 現在のノードを訪問
    visited.add(startNode);
    console.log(`訪問: ${startNode}`);
    
    // 隣接ノードを再帰的に探索
    const neighbors = graph[startNode] || [];
    for (const neighbor of neighbors) {
        if (!visited.has(neighbor)) {
            dfsRecursive(graph, neighbor, visited);
        }
    }
}

// スタックによる反復実装
function dfsIterative(graph, startNode) {
    const visited = new Set();
    const stack = [startNode];
    
    while (stack.length > 0) {
        const currentNode = stack.pop();
        
        if (!visited.has(currentNode)) {
            visited.add(currentNode);
            console.log(`訪問: ${currentNode}`);
            
            // 隣接ノードをスタックに追加(逆順で追加)
            const neighbors = graph[currentNode] || [];
            for (let i = neighbors.length - 1; i >= 0; i--) {
                if (!visited.has(neighbors[i])) {
                    stack.push(neighbors[i]);
                }
            }
        }
    }
}

// 使用例
const graph = {
    A: ['B', 'C'],
    B: ['D', 'E'],
    C: ['F'],
    D: [],
    E: ['F'],
    F: []
};

console.log("=== 再帰実装 ===");
dfsRecursive(graph, 'A');

console.log("=== 反復実装 ===");
dfsIterative(graph, 'A');

// パス探索版(目標ノードまでの経路を発見)
function findPath(graph, start, target, visited = new Set(), path = []) {
    visited.add(start);
    path.push(start);
    
    if (start === target) {
        return path; // 経路を発見
    }
    
    const neighbors = graph[start] || [];
    for (const neighbor of neighbors) {
        if (!visited.has(neighbor)) {
            const result = findPath(graph, neighbor, target, visited, [...path]);
            if (result) {
                return result;
            }
        }
    }
    
    return null; // 経路が見つからない
}

// 連結成分の検出
function getConnectedComponents(graph) {
    const visited = new Set();
    const components = [];
    
    for (const node in graph) {
        if (!visited.has(node)) {
            const component = [];
            dfsComponent(graph, node, visited, component);
            components.push(component);
        }
    }
    
    return components;
}

function dfsComponent(graph, node, visited, component) {
    visited.add(node);
    component.push(node);
    
    const neighbors = graph[node] || [];
    for (const neighbor of neighbors) {
        if (!visited.has(neighbor)) {
            dfsComponent(graph, neighbor, visited, component);
        }
    }
}

🔄 再帰実装 vs 反復実装

再帰実装の特徴

  • • 直感的で理解しやすい
  • • コードが簡潔で美しい
  • • バックトラッキングが自動
  • • 関数型プログラミングと相性良
  • • 深いグラフでスタックオーバーフローリスク

反復実装の特徴

  • • スタックオーバーフロー回避
  • • メモリ使用量の制御が容易
  • • 大規模グラフでも安全
  • • 探索過程の一時停止・再開が可能
  • • 明示的なスタック管理が必要

🚀 深さ優先探索の応用分野

基本的な応用

  • • 迷路の解法
  • • 経路探索
  • • 連結性の判定
  • • 循環検出
  • • 木の走査

アルゴリズム応用

  • • トポロジカルソート
  • • 強連結成分の検出
  • • 橋・関節点の発見
  • • 最小全域木(Kruskal)
  • • バックトラッキング

実世界の応用

  • • ウェブクローリング
  • • ファイルシステム探索
  • • ゲームAI(ゲーム木探索)
  • • SNSの友達関係分析
  • • 依存関係解析