アルゴリズム レッスン3
探索アルゴリズム
線形探索、二分探索、BFS、DFSを理解しよう
線形探索(Linear Search)
配列の先頭から順番に1つずつ確認していく最もシンプルな探索方法です。 計算量は O(n) で、ソートされていないデータにも使えます。
function linearSearch(arr, target) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === target) {
return i; // 見つかったインデックスを返す
}
}
return -1; // 見つからなかった
}
const data = [4, 2, 7, 1, 9, 3];
console.log(linearSearch(data, 7)); // 2
console.log(linearSearch(data, 5)); // -1二分探索(Binary Search)
ソート済みの配列に対して、 中央の要素と比較して探索範囲を半分に絞り込んでいく方法です。 計算量は O(log n) で、線形探索よりはるかに高速です。
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid; // 見つかった
} else if (arr[mid] < target) {
left = mid + 1; // 右半分を探索
} else {
right = mid - 1; // 左半分を探索
}
}
return -1; // 見つからなかった
}
// ソート済みの配列が必要
const sorted = [1, 3, 5, 7, 9, 11, 13, 15];
console.log(binarySearch(sorted, 7)); // 3
console.log(binarySearch(sorted, 6)); // -1100万件のデータでも最大約20回の比較で見つかります(2^20 > 1,000,000)。
幅優先探索(BFS: Breadth-First Search)
グラフや木構造を探索するアルゴリズムで、スタート地点に近いノードから順番に探索します。 キューを使って実装し、最短経路を見つけるのに適しています。
function bfs(graph, start) {
const visited = new Set();
const queue = [start];
const result = [];
visited.add(start);
while (queue.length > 0) {
const node = queue.shift(); // 先頭を取り出す
result.push(node);
for (const neighbor of graph[node]) {
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push(neighbor);
}
}
}
return result;
}
// グラフの隣接リスト表現
const graph = {
A: ["B", "C"],
B: ["A", "D", "E"],
C: ["A", "F"],
D: ["B"],
E: ["B", "F"],
F: ["C", "E"],
};
console.log(bfs(graph, "A"));
// ["A", "B", "C", "D", "E", "F"]深さ優先探索(DFS: Depth-First Search)
1つの経路をできるだけ深く進み、 行き止まりになったら戻って別の経路を探索します。 スタックまたは再帰を使って実装し、全経路の探索やサイクル検出に適しています。
// 再帰版DFS
function dfs(graph, node, visited = new Set()) {
visited.add(node);
const result = [node];
for (const neighbor of graph[node]) {
if (!visited.has(neighbor)) {
result.push(...dfs(graph, neighbor, visited));
}
}
return result;
}
// スタック版DFS
function dfsIterative(graph, start) {
const visited = new Set();
const stack = [start];
const result = [];
while (stack.length > 0) {
const node = stack.pop(); // 末尾を取り出す
if (visited.has(node)) continue;
visited.add(node);
result.push(node);
// 隣接ノードをスタックに追加
for (const neighbor of graph[node]) {
if (!visited.has(neighbor)) {
stack.push(neighbor);
}
}
}
return result;
}
const graph = {
A: ["B", "C"],
B: ["A", "D", "E"],
C: ["A", "F"],
D: ["B"],
E: ["B", "F"],
F: ["C", "E"],
};
console.log(dfs(graph, "A"));
// ["A", "B", "D", "E", "F", "C"]BFS vs DFS の比較
BFS(幅優先)
- 近いノードから探索
- キューを使用
- 最短経路を保証
- メモリ使用量が多い(幅に比例)
- 用途: 最短経路、レベル順走査
DFS(深さ優先)
- 深いノードから探索
- スタック or 再帰を使用
- 最短経路は保証しない
- メモリ使用量が少ない(深さに比例)
- 用途: 全探索、サイクル検出、迷路
まとめ
- 線形探索は O(n) で、ソートされていないデータに使える
- 二分探索は O(log n) で、ソート済みデータに対して非常に高速
- BFS はキューを使い、最短経路を見つけるのに最適
- DFS はスタック/再帰を使い、全経路の探索に向いている
- 問題の性質に応じて適切な探索アルゴリズムを選択する