場合の数と順列
場合の数とは
「コインを投げて表が出る確率は?」と聞かれたとき、まず「全部で何通りの結果があるか」を数える必要があります。コインを投げると「表」か「裏」の2通りですね。表になる場合は1通りなので確率は 。これが場合の数の考え方の出発点です。
確率・統計の世界では、「何通りあるか」を正確に数えることがすべての基礎になります。この第1回では、場合の数を体系的に数えるための道具——積の法則・階乗・順列——を学びます。
積の法則(掛け算の原理)
出来事Aがm通り、Aが起きた後に出来事Bがn通りあるとき、AとBを続けて行う方法は m × n 通り。
たとえば、朝の服装を選ぶとき:シャツが3色(赤・青・緑)、ズボンが2種類(白・黒)あるとします。どのシャツとズボンの組み合わせが選べるか?
「赤シャツ×白ズボン」「赤シャツ×黒ズボン」「青シャツ×白ズボン」……と書いていくと全部で6通り。これは で求められます。
例:シャツが3色、ズボンが2種類あるとき、組み合わせは 通り。
この「掛け算で数える」感覚が確率計算の根本です。
樹形図で積の法則を「見る」
下のデモは「3つの選択肢 × 2つの選択肢」の樹形図をアニメーション表示します。「スタート」から枝が3本出て、それぞれからさらに2本ずつ枝が出る——木の枝のように広がるので「樹形図」と呼びます。最終的な枝の本数が場合の数です。
var t = 0;
var choices1 = ['赤', '青', '緑'];
var choices2 = ['A', 'B'];
var colors1 = ['#ef4444', '#3b82f6', '#22c55e'];
function loop() {
ctx.clearRect(0, 0, W, H);
t += 0.012;
var progress = Math.min(1, t);
var rootX = 80, rootY = H / 2;
var mid1X = 220;
var endX = 420;
ctx.fillStyle = '#fbbf24';
ctx.beginPath();
ctx.arc(rootX, rootY, 10, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#e2e8f0';
ctx.font = 'bold 13px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('スタート', rootX, rootY + 26);
for (var i = 0; i < choices1.length; i++) {
var y1 = (i - 1) * 90 + rootY;
var p1 = Math.min(1, progress * 3 - i * 0.5);
if (p1 <= 0) continue;
var cx1 = rootX + (mid1X - rootX) * p1;
var cy1 = rootY + (y1 - rootY) * p1;
ctx.strokeStyle = colors1[i];
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(rootX, rootY);
ctx.lineTo(cx1, cy1);
ctx.stroke();
if (p1 >= 1) {
ctx.fillStyle = colors1[i];
ctx.beginPath();
ctx.arc(mid1X, y1, 10, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.font = 'bold 12px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(choices1[i], mid1X, y1 + 4);
for (var j = 0; j < choices2.length; j++) {
var y2 = y1 + (j - 0.5) * 50;
var p2 = Math.min(1, (progress - 0.4) * 4 - i * 0.3 - j * 0.15);
if (p2 <= 0) continue;
var cx2 = mid1X + (endX - mid1X) * p2;
var cy2 = y1 + (y2 - y1) * p2;
ctx.strokeStyle = colors1[i];
ctx.lineWidth = 1.5;
ctx.setLineDash([4, 3]);
ctx.beginPath();
ctx.moveTo(mid1X, y1);
ctx.lineTo(cx2, cy2);
ctx.stroke();
ctx.setLineDash([]);
if (p2 >= 1) {
ctx.fillStyle = colors1[i];
ctx.beginPath();
ctx.arc(endX, y2, 8, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.font = '11px sans-serif';
ctx.fillText(choices1[i] + choices2[j], endX, y2 + 4);
}
}
}
}
ctx.fillStyle = '#94a3b8';
ctx.font = '13px sans-serif';
ctx.textAlign = 'left';
ctx.fillText('3 × 2 = 6 通り', 460, H / 2 + 6);
if (t > 3) t = 0;
requestAnimationFrame(loop);
}
loop(); 階乗 n!
階乗(ファクトリアル)とは、1からnまでの整数をすべて掛け合わせたものです。
n! = n × (n-1) × (n-2) × … × 2 × 1
| n | n! |
|---|---|
| 0 | 1(定義) |
| 1 | 1 |
| 2 | 2 |
| 3 | 6 |
| 4 | 24 |
| 5 | 120 |
| 10 | 3,628,800 |
なぜ使う? 異なるn個のものを一列に並べると 通りあるからです。
たとえば「A, B, C, D, E」の5人を一列に並べる場合を数えてみましょう。
- 1番目の席:5人の誰でもOK → 5通り
- 2番目の席:残り4人から → 4通り
- 3番目の席:残り3人から → 3通り
- 4番目の席:残り2人から → 2通り
- 5番目の席:最後の1人 → 1通り
全部で 通りです。
順列 P(n, r)
順列(じゅんれつ)とは、n個の異なるものからr個を選んで順序を考えて並べる方法の数です。「1位・2位・3位を決める」ような「誰がどの順番か」が重要な選び方です。
例:5人の中から3人を選んで1位・2位・3位を決める場合、
「最初の席は5人から選べる → 次の席は残り4人から → さらに次は3人から」という掛け算です。
P(5,3) をスロットで体感する
下のデモでは、5人の中から3つの席(1位・2位・3位)に順番に人が選ばれていきます。1位は5通り、2位は残り4通り、3位は残り3通り——合計で 通りです。
var t = 0;
var names = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve'];
var slotColors = ['#f59e0b', '#8b5cf6', '#06b6d4'];
var chosen = [-1, -1, -1];
var phase = 0;
var phaseT = 0;
var available = [0, 1, 2, 3, 4];
function reset() {
chosen = [-1, -1, -1];
available = [0, 1, 2, 3, 4];
phase = 0;
phaseT = 0;
}
function loop() {
ctx.clearRect(0, 0, W, H);
t += 0.016;
phaseT += 0.016;
if (phase < 3 && phaseT > 1.2) {
var idx = Math.floor(Math.random() * available.length);
chosen[phase] = available[idx];
available.splice(idx, 1);
phase++;
phaseT = 0;
}
if (phase >= 3 && phaseT > 2.0) {
reset();
}
ctx.fillStyle = '#e2e8f0';
ctx.font = 'bold 16px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('P(5, 3) = 5 × 4 × 3 = 60 通り', W / 2, 36);
var labels = ['1位', '2位', '3位'];
var counts = [5, 4, 3];
for (var s = 0; s < 3; s++) {
var sx = 100 + s * 150;
var sy = 100;
ctx.strokeStyle = slotColors[s];
ctx.lineWidth = 3;
ctx.strokeRect(sx - 50, sy, 100, 60);
ctx.fillStyle = slotColors[s] + '33';
ctx.fillRect(sx - 50, sy, 100, 60);
ctx.fillStyle = slotColors[s];
ctx.font = '13px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(labels[s], sx, sy - 8);
ctx.fillText(counts[s] + '通り', sx, sy + 80);
if (s < phase) {
ctx.fillStyle = '#fff';
ctx.font = 'bold 18px sans-serif';
ctx.fillText(names[chosen[s]], sx, sy + 36);
} else if (s === phase) {
var blink = Math.floor(phaseT * 4) % 2 === 0;
if (blink) {
ctx.fillStyle = '#ffffff88';
ctx.font = '14px sans-serif';
ctx.fillText('選択中...', sx, sy + 36);
}
}
}
ctx.fillStyle = '#94a3b8';
ctx.font = '13px sans-serif';
ctx.textAlign = 'center';
var prod = '';
var val = 1;
for (var k = 0; k < 3; k++) {
if (k > 0) prod += ' × ';
prod += (5 - k);
val *= (5 - k);
}
ctx.fillText(prod + ' = ' + val, W / 2, 240);
ctx.fillStyle = '#64748b';
ctx.font = '12px sans-serif';
ctx.fillText('(しばらくするとリセットされます)', W / 2, 270);
requestAnimationFrame(loop);
}
loop(); 順列の公式:なぜ (n-r)! で割るのか
の意味を考えましょう。
- は「n個全部を並べた場合の数」
- しかし実際に並べるのは r 個だけなので、残りの (n-r) 個の並び方は無視する
- 無視したい並び方の数が 通りなので、割る
直感的に言えば、「5人全員の並び方 を求めてから、使わない残り2人の並び替えは区別しない( で割る)」ということです。
具体的には:
練習問題
問1. 0〜9の10枚の数字カードから3枚を選んで3桁の数を作る。何通りあるか(先頭は0以外)?
先頭の選択:9通り(1〜9)、2桁目:9通り(0含む残り9枚)、3桁目:8通り 通り
問2. 男子4人・女子3人から3人を選んで一列に並べる。何通りあるか?
通り
まとめ
- 積の法則:「シャツ3種×ズボン2種=6通り」のように、独立した選択の場合の数は掛け算で求める
- 階乗 n!:n個を全部並べる方法は 。5人の並び方は 通り
- 順列 P(n,r):「1位・2位・3位を決める」ような順序ありの選び方。
- 「スロットを順番に埋める」イメージが順列の直感——各スロットで選べる数が1つずつ減っていく
次回は、順序を考えない選び方——組み合わせ C(n,r)——を学びます。「誰が選ばれるか」だけが重要で「順番は関係ない」場面です。