0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Fisher–Yatesシャッフル j値の範囲 拡大と縮小の比較

0
Last updated at Posted at 2026-05-28

Fisher–Yates シャッフルは、配列を完全にランダムに並べ替える ためのアルゴリズムです。

このアルゴリズムの核心は、ランダムに選ぶ j の取りうる範囲がループごとに拡大(縮小)するという点にあります。

function shuffleArray(array) {
  for (let i = 0; i < array.length; i++) {       //配列を前から取得する版
//  for (let i = array.length - 1; i > 0; i--) { //配列を後ろから取得する版

//以下は前も後ろもおなじ
    const j = Math.floor(Math.random() * (i + 1)); //(i + 1)が増加(減少)するので J値の範囲も拡大(縮小)する
    [array[i], array[j]] = [array[j], array[i]];  //[a,b] = [b,a]
  }
  return array;
}

j値の範囲 を拡大させていく実装(前から進む版)

  • for (let i = 0; i < array.length; i++) {
    • j = Math.floor(Math.random() * i + 1)

j値の範囲 を縮小させていく実装

  • for (let i = array.length - 1; i > 0; i--) {
    • j = Math.floor(Math.random() * (i + 1))
      • iが 5->4->3->2->1 と 1まで減少していく

どちらも数学的には同じ動きをしており、

両方の実装は Fisher–Yates シャッフルの正しいバリエーションであり、
どちらを使っても全ての順列が等確率で生成されます。

現在一般的なのは「j の範囲を縮小させる後ろから前へ進む実装」ですが、
これは古い環境での実装のしやすさや、誤実装を避けやすいという理由から
広く普及したものです。

forward版
// ❌ 間違ったシャッフル
for (let i = 0; i < array.length; i++) {
// (i + 1) iが増加していくことで jの範囲も拡大
  const j = Math.floor(Math.random() * array.length); // ← 全範囲を選んでしまう
  [array[i], array[j]] = [array[j], array[i]];
}

一方、現代の JavaScript ではどちらの書き方も問題なく使えるため、
アルゴリズムの流れを理解しやすい “j の範囲が前へ進むほど広がる実装”
を選ぶのも自然な選択です。
他にも以下の利点がある

  • 配列の長さがわからなくても動く
  • ストリーム(逐次読み込み)でもシャッフルできる
  • メモリ効率が良い

🪑 席替えで理解する Fisher–Yates(j値の拡大版)

4人のメンバー [A, B, C, D] の席替えを行います。
席は 0〜3 の4つあり、最初は A〜D がそのまま 0〜3 の席に座っているものとします。

席替えルール
① 順番は前のA君から B → C → D の順
② くじ箱の中身はからっぽ
③ なので、各自、自分の席番号をくじにいれてからくじを引く
④ くじを引いたら番号を確認し、 引いた番号の席の人と自分の席を交換する
⑤ 引いたくじは箱にもどす

① 前から順番に、1人ずつ席を選ぶ

A → B → C → D の順番で席を決めます。

③ 各自、自分の席番号をくじにいれてからくじを引く

  • A(1人目) → 選べる席は 席0 のみ
    → くじは [0] だけ

  • B(2人目) → 選べる席は 席0 か 席1
    → くじは [0,1]

  • C(3人目) → 選べる席は 席0〜2
    → くじは [0,1,2]

  • D(4人目) → 選べる席は 席0〜3
    → くじは [0,1,2,3]

④ 引いた席の人と自分の席と交換

🔍 例:席[1]のB が席[0]を引いた場合

  • 席0には A が座っている
  • B が席[0]に座る
  • A は B が元いた席 席[1] へ移動する
    → これが 交換 です

ゲームの開始

1人目:Aくんが席を決めます(i = 0)

Aくんは最初から [0] に座っています

  • 選べる席0 のみ
  • くじ引き0
  • 結果:そのまま席 [0] に座り続けます

現在の状態[ A ]


2人目:Bくんが席を決めます(i = 1)

ここで Bくんの席 [1] が新しく追加されます

  • 選べる席0 または 1
  • くじ引き0, 1

もし 0 を引いた場合:
→ 席 [0] に座っている Aくんと席を交換(swap) します。

現在の状態(例)[ B, A ]


3人目:Cくんが席を決めます(i = 2)

次に Cくんの席 [2] が追加されます

  • 選べる席0, 1, 2
  • くじ引き0, 1, 2

もし 1 を引いた場合:
→ 席 [1] に座っている Aくんと席を交換(swap) します。

現在の状態(例)[ B, C, A ]


4人目:Dくんが席を決めます(i = 3)

最後に Dくんの席 [3] が追加されます

  • 選べる席0, 1, 2, 3
  • くじ引き0, 1, 2, 3

もし 3(自分の席) を引いた場合:
→ 誰とも交換せず、そのまま座ります。

最終状態(例)[ B, C, A, D ]

🪑 席替えのルールで理解する(j値の範囲が縮小する版)

4人のメンバー [A, B, C, D] は、最初は席 [0,1,2,3] にそのまま座っています。

j 値が縮小する方法(後ろから前へ進む Fisher–Yates)では、
後ろの席から順番に、だれが座るかを 先生がくじ箱(A,B,C,Dの票が入っている)から票を引いて決めていきます。

席3 → 席2 → 席1 の順に、先生がくじを引き、
引かれた人をその席に座らせます。

ただし、生徒は最初から席に座っているため、
引かれた人と すでに席に座っている人と入れ替え(swap)が発生します。

最後の席0は、選ばれなかった人がそのまま座ります。

① 後ろの席から順番に誰がすわるか決める

席3 → 席2 → 席1 の順番で
「この席に誰が座るか」を先生がくじ引きで決めていきます。

よって席0には、残った人が座ることになります

② 一度引いたくじは箱にはもどさない

くじ箱は 1 つだけで、中には A, B, C, D の票 が 1 枚ずつ入っています。
先生がくじ箱から票を引いたら くじは箱に戻さず、残りの票を引いていくことになります

  • 席[3] を決めるとき
    → 先生がくじ箱から 1 枚引く
    → くじ箱の中身は [A,B,C,D](4票)
    →引かれた人(例:C)は席3に座り、くじ箱から取り除かれる
    →元々席[3]にはDがすわっていたので、Cがすわっていた席[2]にDが移動することで席の交換が生じます

  • 席[2] を決めるとき
    → くじ箱の中身は 残りの3票(例:A,B,D)

  • 席[1] を決めるとき
    → くじ箱の中身は 残りの2票

  • 席[0] はそのまま
    → 席[0]にはいま座っている人がそのまま座ることになります
    → もしAが選ばれなかったらそのまま座り続けることになります。

③ 選ばれた人がその席に座る(すでに誰かが座っていた場合は交換)

その席に選ばれた人がその席にすわるため、もともとその席に座っていた人は、選ばれた人と席を交換することになります。

🎮 ゲーム開始

① 席[3] を決める(最初のくじ)

  • くじ箱の中身:[A, B, C, D]
  • 先生が 1 枚引く(例:C

すると:

  • 席3には D が座っている
  • C を席3に座らせるため、C と D が席を交換する

状態例[A, B, D, C]

② 席[2] を決める(次のくじ)

  • くじ箱の中身:[A, B, D](C は取り除かれている)
  • 先生が 1 枚引く(例:A

すると:

  • 席2には D が座っている
  • A を席2に座らせるため、A と D が席を交換する

状態例[D, B, A, C]

③ 席[1] を決める(次のくじ)

  • くじ箱の中身:[B, D]
  • 先生が 1 枚引く(例:B

すると:

  • 席1には B が座っている
  • すでに B が座っているので 交換は起きない

状態例[D, B, A, C]

④ 席[0] はそのまま

  • そのまま D が座る
  • くじ箱の中身:[D]

🏃‍♂️ 結論:後ろからでも前からでも結果は同じ!

  • シャッフルの公平さ:完全に等確率(同じランダム度) 🎰
  • 処理スピード(性能):どちらも $O(N)$ で全く同じ ⚡
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?