JavaScript | 「再帰関数」でツリー探索(Tree Traversal)

JavaScript JavaScript
スポンサーリンク

では今回は、再帰の「本領発揮」分野である ツリー探索(Tree Traversal) を、プログラミング初心者でも「実感して理解できる」ように、段階的にやさしく解説します。


1. まず「ツリー構造」ってなに?

ツリー構造とは、「親 → 子 → 孫…」のように階層があるデータのことです。
たとえばパソコンのフォルダ構成:

root
├─ photos
│   ├─ trip
│   └─ family
└─ docs
    ├─ work
    └─ personal

JavaScriptで表すと👇のようになります:

const tree = {
  name: "root",
  children: [
    {
      name: "photos",
      children: [
        { name: "trip", children: [] },
        { name: "family", children: [] }
      ]
    },
    {
      name: "docs",
      children: [
        { name: "work", children: [] },
        { name: "personal", children: [] }
      ]
    }
  ]
};
JavaScript

2. 「ツリー探索」とは?

ツリーの中を「全部たどる」ことです。
たとえば「すべてのフォルダ名を表示したい」など。

ツリー探索には2つの代表的な方法があります:

方法呼び出し順よく使う場面
深さ優先探索(DFS)子ノードをどんどん掘り下げてから戻る構造を全部調べたい時
幅優先探索(BFS)同じ階層を順に処理「距離の近い順」で探す時

ここでは 再帰を使った「深さ優先探索」 に集中します。


3. 再帰でツリーをたどる(基本版)

function traverse(node) {
  console.log(node.name); // まず自分を処理
  for (const child of node.children) {
    traverse(child); // 子を再帰的に探索
  }
}

traverse(tree);
JavaScript

出力:

root
photos
trip
family
docs
work
personal

🟢 これが最も基本的な「深さ優先探索(DFS)」の流れです。


4. 特定のノードを検索する再帰関数

「名前が work のノードを探す」関数を作ってみましょう。

function findNode(node, targetName) {
  if (node.name === targetName) {
    return node; // 見つけた!
  }

  for (const child of node.children) {
    const result = findNode(child, targetName);
    if (result) return result; // 子の中で見つかったら即返す
  }

  return null; // 見つからなかった
}

const result = findNode(tree, "work");
console.log(result);
JavaScript

出力:

{ name: "work", children: [] }

ポイント解説

  • if (node.name === targetName) が「終了条件(ベースケース)」
  • 見つからなければ子ノードを再帰的に探す
  • return result で「見つかったらすぐ戻る」

5. 全ノードを「リスト」として集める

フォルダ名をまとめて配列にして返す関数:

function collectNames(node) {
  let names = [node.name]; // 自分の名前をまず追加
  for (const child of node.children) {
    names = names.concat(collectNames(child)); // 子の名前を結合
  }
  return names;
}

console.log(collectNames(tree));
// ["root", "photos", "trip", "family", "docs", "work", "personal"]
JavaScript

🟡 こうすれば「再帰呼び出しの結果(子の配列)」をまとめて扱えます。


6. 応用:階層をインデントして表示する

「どのフォルダがどの階層にあるか」をわかりやすく出力する例です。

function printTree(node, depth = 0) {
  console.log(" ".repeat(depth * 2) + node.name); // 深さに応じてインデント

  for (const child of node.children) {
    printTree(child, depth + 1); // 深さを1つ増やして呼ぶ
  }
}

printTree(tree);
JavaScript

出力:

root
  photos
    trip
    family
  docs
    work
    personal

📘 コツ:

  • 再帰では「深さ(depth)」のような補助的な引数を使うと、階層情報を簡単に扱える。
  • depth + 1 で階層を一段深くしていくのが再帰らしいポイントです。

7. 実務に近い使い方(再帰+条件)

「ファイルの中から .txt だけをリストアップしたい」など、条件を加えた探索。

const fileTree = {
  name: "root",
  children: [
    { name: "note.txt", children: [] },
    { name: "photo.jpg", children: [] },
    { name: "sub", children: [
        { name: "report.txt", children: [] },
        { name: "data.json", children: [] }
      ]
    }
  ]
};

function findTxtFiles(node) {
  let result = [];
  if (node.name.endsWith(".txt")) {
    result.push(node.name);
  }

  for (const child of node.children) {
    result = result.concat(findTxtFiles(child));
  }

  return result;
}

console.log(findTxtFiles(fileTree));
// ["note.txt", "report.txt"]
JavaScript

🔹 再帰の中で条件をチェックし、該当したものだけ配列に追加。
🔹 実務では「ファイル検索」「メニュー階層の生成」「コメントツリー」などでよく使われます。


8. まとめ

ポイント内容
🌿 再帰は「ツリー」を扱うのが得意子を同じ形で処理する構造にピッタリ
🧱 ベースケース(終了条件)子がいない、見つけた、などで終了
🔁 引数で状態を渡すdepthpath を渡すことで柔軟に
📦 配列やオブジェクトを結合結果をまとめて返す構成にする

追加チャレンジ(自作課題)

  1. ファイルパス付きで出力する
     → root/photos/trip のように親フォルダも含めた文字列を出力する。
  2. ノードの数を数える
     → 再帰を使ってツリー全体にいくつのノードがあるかを数える。
  3. 深さの最大値を求める
     → 「最も深い階層はいくつか?」を調べる。

タイトルとURLをコピーしました