Skip to content

アルゴリズム思考入門

前書き

どうすれば効率的に問題を解決できるか? こんな悩みを持ったことはありませんか:同じ問題でも、ある人のコードは数秒で結果が出るのに、別の人のコードは数分経ってもまだ動き続けている。その差は多くの場合アルゴリズムにあります。この章では、アルゴリズムの核心的な考え方を理解していきます。

この記事で学べること

この章を学び終えると、次のスキルが身につきます:

  • 問題分割能力:複雑な問題に直面したとき、すぐにコードを書き始めるのではなく、分割統治や再帰などの戦略で分解して考えられるようになる
  • 効率判断能力:ビッグO記法を使って2つの解法のどちらがより効率的かを判断でき、感覚的な推測に頼らなくなる
  • 計算量思考:コードを書く前にデータ規模と時間要件を見積もり、適切なアルゴリズムレベルを選択できるようになる
  • 後続学習の基礎:高度なデータ構造、分散システム、機械学習の基礎を築く
内容コアコンセプト
第1章二分探索分割統治の考え方、O(log n)
第2章ソートアルゴリズムバブルソート、クイックソート、マージソート
第3章計算量分析時間計算量、空間計算量

0. 全景図:アルゴリズムとは?

辞書から単語を探す場面を想像してください:

  • 方法1:最初のページから1ページずつめくる(線形探索)
  • 方法2:頭文字で位置を特定し、さらに二分探索する(二分探索)

どちらの方法でも見つかりますが、効率は天と地ほどの差があります。アルゴリズムとは問題を解決する方法です

Algorithmic Thinking: Ways to Solve ProblemsDifferent strategies fit different kinds of problems
Binary searchEliminate half each time, O(log n)
Time Complexity Quick Reference
O(1)ConstantBest, such as array access
O(log n)LogarithmicVery good, such as binary search
O(n)LinearCommon, such as traversal
O(n log n)LinearithmicAcceptable, such as quicksort
O(n²)QuadraticSlow, such as bubble sort
O(2ⁿ)ExponentialVery slow, such as brute-force recursion
Core idea:Algorithms are methods for solving problems. A good algorithm can improve efficiency by orders of magnitude. Understanding algorithmic thinking matters more than memorizing individual algorithms.

アルゴリズムの核心指標:

指標意味重要である理由
時間計算量データ量の増加に伴う実行時間の増加傾向大規模データでの性能を予測
空間計算量データ量の増加に伴うメモリ使用量の増加傾向メモリ消費を評価
正当性常に正しい結果を得られるかアルゴリズムの基本要件

📊 この表の行ごとの解説

時間計算量:ビッグO記法で記述します。O(n)はデータ量が2倍になると時間も2倍になることを意味し、O(n²)はデータ量が2倍になると時間が4倍になることを意味します。

空間計算量:同様にビッグO記法を使います。空間を時間と交換するアルゴリズム(ハッシュテーブルなど)もあれば、時間を空間と交換するアルゴリズム(圧縮アルゴリズムなど)もあります。

正当性:アルゴリズムはすべての可能な入力に対して正しい結果を出せなければなりません。境界条件(空入力、極大入力)が最もエラーを起こしやすい部分です。


1. 二分探索:毎回半分を除外

1.1 二分探索の原理

💡 二分探索の仕組み

前提:データがソート済みであること

手順

  1. 中央の要素を見つける
  2. 中央の要素が目標と等しければ、発見!
  3. 目標が中央の要素より小さければ、左半分で続行
  4. 目標が中央の要素より大きければ、右半分で続行
  5. 毎回半分を除外し、見つかるか存在しないと確定するまで繰り返す

時間計算量:O(log n)

日常での例え:数字当てゲーム。私が1〜100の数字を思い浮かべ、あなたが毎回中央値を推測し、私が大きいか小さいかを答えます。最大7回で当てられます(2⁷ = 128 > 100だから)。

👇 実際に試してみよう: 以下のデモは二分探索の動作原理を示しています。線形探索と二分探索を選択して比較できます:

Search AlgorithmsHow to find a target in data
Linear search: check items one by one
3
7
2
9
5
1
8
4
6
10
Target number:
Time complexity: O(n)
Use case: unordered arrays
Performance Comparison
Data sizeLinear searchBinary search
10At most 10 stepsAt most 4 steps
100At most 100 stepsAt most 7 steps
1000At most 1000 stepsAt most 10 steps
10000At most 10000 stepsAt most 14 steps

1.2 二分探索がこんなに速い理由

データ量線形探索二分探索
100100回7回
1,0001,000回10回
1,000,0001,000,000回20回
1,000,000,0001,000,000,000回30回

📊 この表の行ごとの解説

1列目(データ量):検索対象のデータ数です。データ量が100から10億に増加していることがわかります(1000万倍に拡大!)

2列目(線形探索):最も「愚直な」方法で、最初から1つずつ探します。検索回数はデータ量に等しく、データ量が多いほど検索回数も増えます。

3列目(二分探索):賢い方法で、毎回半分を除外します。検索回数はデータ量の対数にのみ関係し、10億のデータでもわずか30回で済みます!

比較の結論:データ量が100万に達すると、線形探索は100万回必要ですが、二分探索はわずか20回で済みます——その差は5万倍にもなります!

📊 対数成長の威力

二分探索の時間計算量はO(log n)です。これは次のことを意味します:

  • 10億のデータでも、最大30回の検索
  • 1兆のデータでも、最大40回の検索

これが対数成長の威力です——データ量が1000倍に増えても、検索回数はわずか10回しか増えません。


2. ソート:無秩序を秩序に変える

2.1 主なソートアルゴリズム

アルゴリズム時間計算量特徴適用シーン
バブルソートO(n²)単純だが遅い教育、小規模データ
選択ソートO(n²)単純だが遅い小規模データ
挿入ソートO(n²)ほぼソート済みデータに高速小規模データ、ほぼソート済み
クイックソートO(n log n)実用上最速汎用ソート
マージソートO(n log n)安定ソート安定性が必要な場面
ヒープソートO(n log n)インプレースソートメモリ制約のある場面

📊 この表の行ごとの解説

バブルソート:最も基本的なソートアルゴリズムで、水底の泡が浮かび上がるような動きをします。理解しやすいですが、速度は最も遅いです。ソートの考え方を学ぶのに適していますが、実用には向きません。

選択ソート:毎回最小のものを選んで前に配置します。これも単純ですが、データがソート済みかどうかに関わらず同じ回数の比較を行います。

挿入ソート:トランプで手札を整理するようなものです。各要素を前方のソート済み部分に挿入します。ほぼソート済みのデータに対して効率が高いです。

クイックソート:実際の開発で最もよく使われるソートです。平均的には最速ですが、最悪の場合(データがすでにソート済み)はO(n²)に劣化します。

マージソート:「分割統治」の考え方を採用し、常にO(n log n)ですが、追加の空間が必要です。安定ソートが必要な場面に適しています。

ヒープソート:ヒープというデータ構造を利用したソートで、インプレースソート(追加空間不要)ですが、実際の実行速度はクイックソートより遅いことが多いです。

2.2 クイックソートが「速い」理由

💡 クイックソートの原理

核心的な考え方:分割統治法

  1. 「基準」要素を1つ選ぶ
  2. 基準より小さいものを左に、基準より大きいものを右に配置
  3. 左右の部分を再帰的にソート
  4. 結果を統合

なぜ速いのか?

  • 毎回の分割後、基準要素は最終位置に到達する
  • 平均的には、毎回の分割で約半分の要素が除外される
  • 時間計算量 O(n log n)

日常での例え:本棚の整理。まず1冊取り出し、それより薄い本を左に、厚い本を右に置きます。そして左右の山に対して同じ手順を繰り返します。

👇 実際に試してみよう: 以下のデモはソートアルゴリズムの可視化を示しています。配列を生成し、バブルソートとクイックソートの過程を比較できます:

Sorting AlgorithmsPut data into order
50
30
70
40
90
20
60
80
10
55
Choose a sorting algorithm
Select a sorting algorithm to start the demo
Time complexity:
Algorithm Comparison
AlgorithmAverage timeWorst timeSpaceStable
Bubble sortO(n²)O(n²)O(1)
Quick sortO(n log n)O(n²)O(log n)
Merge sortO(n log n)O(n log n)O(n)
Insertion sortO(n²)O(n²)O(1)

3. 再帰:自分自身を呼び出す

3.1 再帰の本質

💡 再帰とは?

再帰は関数が自分自身を呼び出すプログラミング技法です。

2つの重要な要素

  1. ベースケース:いつ再帰を停止するか?
  2. 再帰ステップ:問題をより小さな部分問題にどう分解するか?

古典的な例:階乗

js
function factorial(n) {
  if (n <= 1) return 1        // ベースケース
  return n * factorial(n - 1) // 再帰ステップ
}

日常での例え:ロシアのマトリョーシカ人形。人形を開けると、中にさらに小さい人形が入っており、一番小さい人形が開けられなくなるまで続きます。

3.2 再帰 vs 反復

特性再帰反復(ループ)
コードの簡潔さ通常より簡潔より複雑になりがち
メモリ消費高い(コールスタック)低い
パフォーマンスやや遅い(関数呼び出しのオーバーヘッド)より速い
適用シーンツリー走査、分割統治アルゴリズム単純な繰り返しタスク

📊 この表の行ごとの解説

コードの簡潔さ:再帰は通常、数行のコードで複雑なロジックを表現できますが(ツリー構造の走査など)、ループではより多くの変数やネストが必要になる場合があります。

メモリ消費:再帰は「コールスタック」を使って各層の情報を保存します。皿を積み重ねるように、再帰が1層進むごとに皿が1枚増えます。ループにはこのようなオーバーヘッドはありません。

パフォーマンス:関数呼び出しのたびにオーバーヘッド(引数の受け渡し、スタック操作など)が発生するため、再帰は通常ループよりやや遅くなります。

適用シーン:再帰はそれ自体が再帰的な構造を持つ問題(ファイルツリー、DOMツリーなど)の処理を得意とし、ループは単純な繰り返し操作(配列の走査など)を得意とします。

⚠️ 再帰の落とし穴

スタックオーバーフロー:再帰の深さが深すぎると、コールスタックの領域が枯渇します。

解決方法

  • 反復に切り替える
  • 末尾再帰最適化を利用する(一部の言語でサポート)
  • 再帰の深さを制限する

👇 実際に試してみよう: 以下のデモは再帰の呼び出し過程を示しています。関数がどのように自分自身を呼び出すかを観察できます:

Recursive Thinking: A Function Calls ItselfBreak a large problem into smaller problems of the same kind
🪆
Nested dolls
Open a large doll and there is a smaller doll inside
Open that one and there is an even smaller one, until the smallest case
That is recursion.
Recursive examples
Factorial: n! = n × (n-1)!
5! = 5 × 4!
4! = 4 × 3!
3! = 3 × 2!
2! = 2 × 1!
1! = 1 (base case)
↑ return 1
↑ return 2 × 1 = 2
↑ return 3 × 2 = 6
↑ return 4 × 6 = 24
↑ return 5 × 24 = 120
Three Elements of Recursion
1
Base case
When should recursion stop? There must be a termination condition.
Example: 1! = 1
2
Recursive call
How does the problem get smaller? Call the same function on a smaller case.
Example: turn n! into (n-1)!
3
Return result
How does the current problem use the result of the subproblem?
Example: the result of n × (n-1)!
✓ Pros
  • Concise code
  • Naturally expresses recursive structures
  • Good for tree and graph traversal
✗ Cons
  • May repeat work
  • Uses stack space
  • Can be harder to debug

4. 貪欲アルゴリズム:毎回最適を選ぶ

4.1 貪欲法の考え方

💡 貪欲アルゴリズムとは?

貪欲アルゴリズムは、各ステップで現在最も最適に見える選択を行い、最終的に大域的最適解を得ることを目指します。

適用条件

  1. 貪欲選択性質:局所的最適が大域的最適につながる
  2. 最適部分構造:問題の最適解が部分問題の最適解を含む

古典的な例:硬貨の両替

  • 目標:最小枚数の硬貨で指定金額を作る
  • 貪欲戦略:毎回最も大きな硬貨を選ぶ
  • 結果:67円 = 50 + 10 + 5 + 1 + 1(5枚)

日常での例え:登山で毎回最も急な道を選んで登るようなものです。必ずしも最高峰にたどり着けるとは限りませんが、通常はかなり良い位置まで行けます。

4.2 貪欲法の限界

⚠️ 貪欲法が常に最適解を得られるとは限らない

反例:硬貨の両替

硬貨の額面が [1, 3, 4] で、6円を作る場合:

  • 貪欲法:4 + 1 + 1 = 3枚
  • 最適解:3 + 3 = 2枚

貪欲アルゴリズムはここで失敗します!

教訓:貪欲アルゴリズムは単純で効率的ですが、常に最適解を得られるとは限りません。使用前に問題が貪欲条件を満たすことを証明する必要があります。

👇 実際に試してみよう: 以下のデモは貪欲アルゴリズムの実際の効果を示しています。異なる硬貨の組み合わせを試し、貪欲戦略のパフォーマンスを観察できます:

Greedy Algorithms: Choose the Best Current MoveLocal optimum → global optimum?
A greedy algorithm chooses the best option available at each step
It hopes a series of local choices reaches a global optimum
Classic problems
Coin Change Problem
Change needed: 37
20
× 1 = 20
10
× 1 = 10
5
× 1 = 5
1
× 2 = 2
Needs 5 coins total
✓ Greedy strategy: choose the largest coin each time
✓ Works for currencies such as RMB and USD
Greedy vs Dynamic Programming
FeatureGreedy algorithmDynamic programming
Decision styleChoose the current best each stepConsider all possibilities and choose the best
OptimalityMay not be globally optimalGuarantees a global optimum
Time complexityO(n) or O(n log n)O(n²) or higher
Best fitLocal optimum → global optimumOverlapping subproblems
✓ Pros
  • Simple to implement
  • Efficient
  • Low space complexity
✗ Cons
  • Does not always guarantee a global optimum
  • Limited applicability
  • Requires an optimality proof

5. アルゴリズム設計パラダイム

パラダイム考え方代表的なアルゴリズム適する問題
分割統治問題を小さな問題に分解クイックソート、マージソート分解可能な問題
貪欲法毎回最適を選ぶ最小全域木、ハフマン符号化貪欲性質を持つ問題
動的計画法部分問題の解を記録ナップサック問題、最短経路重複部分問題がある問題
バックトラッキング試行錯誤、行き詰まったら戻るエイトクイーン、全順列探索問題

📊 この表の行ごとの解説

分割統治:大きな問題を小さな問題に分割し、それぞれ解決してから統合します。部屋の掃除をリビング、寝室、キッチンに分けてそれぞれ掃除し、最後に全体がきれいになるようなものです。

貪欲法:毎回現在最善のものを選び、長期的な結果を考慮しません。食事のときに一番好きな料理から食べるようなもので、最適な食べ方ではないかもしれませんが、スピードは速いです。

動的計画法:中間結果を記憶し、重複計算を避けます。ノートを取るようなもので、次に同じ問題に遭遇したときに直接答えを参照でき、再度導出する必要がありません。

バックトラッキング:行き詰まったら戻って再試行します。迷路を進むようなもので、この道が通れなければ前の交差点に戻って別の道を試します。

👇 実際に試してみよう: 以下のデモは異なるアルゴリズム設計パラダイムの特徴と適用シーンを示しています:

Algorithm Design ParadigmsCommon patterns for solving problems
Algorithm design paradigms are general strategies for solving problems. Learning them helps you find solution ideas quickly.
✂️
Divide and conquer
Divide, solve, combine
📊
Dynamic programming
Store results to avoid repetition
🎯
Greedy
Local optimum
🔙
Backtracking
Try and retreat
✂️Divide and conquer
Core idea
Split a large problem into smaller independent problems, solve them recursively, then combine the results.
Use cases
Array sortingMatrix multiplicationLarge integer arithmetic
Classic problems
📝
Merge sort
📝
Quick sort
📝
Binary search
📝
Strassen matrix multiplication
Time complexity
O(n log n)
Often much faster than brute force
Paradigm Comparison
ParadigmCore strategyOptimalityUse case
✂️ Divide and conquerSplit → recurse → combineGuarantees optimumProblems that split independently
📊 Dynamic programmingStore subproblem answersGuarantees optimumOverlapping subproblems
🎯 GreedyChoose the best each timeNot always optimalLocal optimum → global optimum
🔙 BacktrackingDepth-first searchGuarantees optimumSmall search spaces that need enumeration
How to choose the right paradigm?
1
Analyze problem features
Are there overlapping subproblems? Is there optimal substructure?
2
Decide whether an optimum is required
Greedy is not always optimal; dynamic programming guarantees an optimum.
3
Consider input size
Backtracking fits small inputs, while divide and conquer fits larger inputs.

6. まとめ:アルゴリズムは問題解決の芸術

さまざまなアルゴリズムの考え方を比喩でまとめましょう:

考え方比喩核心ポイント
二分探索数字当て毎回半分を除外
ソート本棚の整理秩序を築く
再帰マトリョーシカ大を小に変える
貪欲法登山の道選び局所的最適

💡 核心的な示唆

アルゴリズムの本質は「効率」と「正当性」のバランスです。

  • 優れたアルゴリズムはプログラムの効率を桁違いに向上させる
  • しかし過度な最適化は複雑さをもたらす可能性がある
  • まず正当性を確保し、それから効率を追求する

アルゴリズムの考え方を理解することは、具体的なアルゴリズムを暗記することよりも重要です:

  • 分割統治:大きな問題を小さな問題に分解する
  • 貪欲法:毎回最適を選ぶ
  • 動的計画法:部分問題の解を記録する
  • バックトラッキング:試行錯誤し、行き詰まったら戻る

参考資料

  • アルゴリズムイントロダクション:アルゴリズムを体系的に学ぶための古典的教科書
  • LeetCode:問題演習を通じてアルゴリズム能力を向上
  • アルゴリズム可視化:アルゴリズムの実行過程を直感的に理解
  • 競技プログラミング:より高度なアルゴリズム技法を学ぶ