線形探索 (O(n))

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

線形探索 (O(n))

このレッスンで分かること

  • 計算量は O(n)
  • 配列の中に「指定の値が含まれているか」「含まれているなら何番目か」を調べたいとき、最もシンプルなアプローチが 線形探索 (linear search) です
  • 図のポイント (テキスト併記)

線形探索 (O(n)) とは

配列の先頭から順に target を探す線形探索を実装し、O(n) の挙動と最悪計算量を理解する。本レッスンでは、線形探索 (O(n)) の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

配列の中に「指定の値が含まれているか」「含まれているなら何番目か」を調べたいとき、最もシンプルなアプローチが 線形探索 (linear search) です。先頭から末尾まで 1 つずつ順番に見て、一致するものが見つかればその位置を返し、見つからなければ -1 を返します。

計算量は O(n) 。配列の長さ n に比例して時間がかかります。n100 でも 100 万でも、最悪の場合は全要素を見ることになる、というのがポイントです。

線形探索は「総当たり」の最たる例。シンプルで、ソートされていない配列でも使える万能戦法。

アルゴリズムの流れ

diagram (will load when visible)

図のポイント (テキスト併記)

  • 図のとおり、配列を先頭から末尾までスキャンし、見つかった瞬間にループを抜けて位置を返します

図のとおり、配列を先頭から末尾までスキャンし、見つかった瞬間にループを抜けて位置を返します。最後まで見つからなければ -1 を返します。

最悪 / 最良 / 平均

線形探索の計算量を整理すると、最良の場合は配列の先頭にあるケースで O(1) 、最悪の場合は末尾にあるか存在しないケースで O(n) 、平均的にはおよそ n/2 回の比較なので O(n) です。

O(n) の意味は「n に比例する」で、定数倍の違い (nn/22n か) は無視する。これが Big-O の特徴。

Python での実装例

Python

def linearSearch(arr, target): for i in range(len(arr)): if arr[i] == target: return i return -1

for i in range(len(arr)) で先頭から末尾までインデックスを順に取り、一致したら return i で即座にループを抜けます。

JavaScript での実装例

JavaScript

function linearSearch(arr, target) { for (let i = 0; i < arr.length; i++) { if (arr[i] === target) { return i; } } return -1; }

比較は === (厳密等価) を使うのが鉄則です。== を使うと "5" == 5 のような型変換が走るため、思わぬバグになります。

よくある間違い

1 つ目は 見つかった瞬間に return せず、ループを最後までまわしてしまう こと。これだと無駄に時間がかかり、複数回マッチした場合に「最後の出現位置」を返してしまいます。本問では「最初に見つけた位置」を返す仕様なので、見つけたら 即座に return します。

2 つ目は 見つからなかったときの -1 を返し忘れること。return Nonereturn undefined ではなく、明示的に -1 を返すのが慣例です。

3 つ目は オフバイワンi <= len(arr) と書いてしまうと IndexError (Python) や undefined (JS) を踏みます。配列のインデックスは 0..len-1 までであることを忘れずに。

やってみよう

  • arr = [3, 1, 4, 1, 5, 9, 2, 6]target = 5 を探すと 4 (0-indexed) が返る。
  • target = 7 のように 存在しない値-1 が返ることを確認する。
  • 配列が n = 1000000 件あるとき、末尾の値を探すと当然 n 回比較する。これが O(n) の最悪ケース。

線形探索は O(n) 。ソートされた配列なら次レッスンの 二分探索 O(log n) を使うのが定石。

Java と Go での実装

Java

public static int linearSearch(int[] arr, int target) { for (int i = 0; i < arr.length; i++) { if (arr[i] == target) { return i; } } return -1; }

Go

func linearSearch(arr []int, target int) int { for i := 0; i < len(arr); i++ { if arr[i] == target { return i } } return -1 }

線形探索が活きる場面

計算量が O(n) と聞くと「遅い」印象を持ちますが、線形探索が向いている場面はたくさんあります。たとえば配列が 小さい とき (n10 以下程度) は、二分探索のために事前にソートする手間の方が高くつきます。また配列が 未ソート で 1 回しか探索しないなら、ソート (O(n log n)) + 二分探索 (O(log n)) より線形探索 (O(n)) の方が速いことすらあります。

アルゴリズム選びは「データのサイズ」「ソート済みか」「何回探索するか」の 3 点で決まる。線形探索を即否定せず、状況で使い分ける感覚を持とう。

Python の in 演算子

Python では target in arr で要素の有無を判定できます。内部では線形探索が走っており、計算量は同じく O(n) です。組み込みなので C で書かれていて定数倍は速いですが、オーダーは変わらないことを意識しましょう。arr.index(target) も同様に O(n) で、見つからない場合は例外を投げます (本問の -1 を返す挙動とは異なる)。

よくある質問

Q. 線形探索と二分探索はどう違う?

A. 線形探索は配列の先頭から順に O(n) で見ます。二分探索はソート済み配列を半分ずつに絞って O(log n) で見つけます。少数の検索なら線形でも問題ありませんが、検索が頻繁ならソート + 二分探索が圧倒的に高速です。

Q. 線形探索の利点は何ですか?

A. ソート不要・実装が単純・追加コスト無しで動く点です。要素数が数十程度なら二分探索より速いこともあります(定数倍の差)。データ構造の選択でメリットが小さい場合、無理に二分探索や hash を使う必要はありません。

Q. 見つからなかったときの返し方は?

A. 言語慣習に従い -1(インデックス)/ null / undefined / None / Optional.empty などを返します。例外を投げる設計も可能ですが、「見つからない」が想定内なら戻り値で表現する方が呼び出し側で扱いやすいです。

次のレッスン

次は 二分探索 (O(log n)) に進みましょう。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. 線形探索 の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. 線形探索 とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. 先頭から末尾まで順に比較する線形探索で実装すること
  2. 見つかった場合は最初に一致した位置 (0-indexed) を返す
  3. 見つからない場合は -1 を返す

入出力例

test-cases.txt

linearSearch([3,1,4,1,5,9,2,6], 5)4 linearSearch([7,1,2,3], 7)0 linearSearch([1,2,3,4], 4)3 linearSearch([1,2,3,4], 99)-1 linearSearch([1,2,3,2,1], 2)1 linearSearch([42], 42)0

ヒント

main.py
main.py
学習モード

メモ

線形探索 (O(n))

⌘S で保存