約瑟夫問題 (Josephus Problem) 是經典的計算機科學與數學問題:N 個人圍成圈,每數到 M 個人淘汰一個,求最後倖存者編號。本文提供 PHP 多種解法實作與效能分析。
n、步長 mm 的人出局,下一人從 1 重新報數<?php
/**
* 約瑟夫環 - 陣列模擬法
* @param int $n 總人數
* @param int $m 步長
* @return int 倖存者編號 (1-based)
*/
function josephus_array($n, $m) {
$people = range(1, $n); // 建立編號陣列 [1, 2, ..., n]
$index = 0; // 當前索引
while (count($people) > 1) {
// 計算要移除的索引: (當前位置 + 步長 - 1) % 當前人數
$index = ($index + $m - 1) % count($people);
array_splice($people, $index, 1); // 移除該人
// $index 自動指向下一人 (因為陣列塌陷)
}
return $people[0];
}
// 測試
echo josephus_array(5, 3); // 輸出: 4
// 過程: [1,2,3,4,5] -> 移除3 -> [1,2,4,5] -> 移除1 -> [2,4,5] -> 移除5 -> [2,4] -> 移除2 -> [4]
<?php
/**
* 約瑟夫環 - 隊列模擬法
* 使用 array_shift/array_push 模擬循環隊列
*/
function josephus_queue($n, $m) {
$queue = range(1, $n);
$count = 0;
while (count($queue) > 1) {
$person = array_shift($queue); // 出隊
$count++;
if ($count == $m) {
// 數到 m,淘汰 (不重新入隊)
$count = 0;
echo "淘汰: $person\n";
} else {
// 未到 m,重新入隊尾部
array_push($queue, $person);
}
}
return $queue[0];
}
<?php
/**
* 約瑟夫環 - 數學遞歸解法
* 公式: f(1,m) = 0; f(n,m) = (f(n-1,m) + m) % n
* 回傳 0-based 索引,+1 得到 1-based 編號
*/
function josephus_math($n, $m) {
$result = 0; // f(1,m) = 0
for ($i = 2; $i <= $n; $i++) {
$result = ($result + $m) % $i;
}
return $result + 1; // 轉為 1-based
}
/**
* 遞歸版本 (注意大 n 可能棧溢出)
*/
function josephus_recursive($n, $m) {
if ($n == 1) return 0;
return (josephus_recursive($n - 1, $m) + $m) % $n;
}
<?php
/**
* 原文猴子版修正:修復變數作用域、輸出格式化
*/
function killMonkeys($monkeys, $m) {
$k = $m;
$eliminated = [];
while (count($monkeys) > 1) {
$k--;
$monkey = array_shift($monkeys);
if ($k > 0) {
$monkeys[] = $monkey; // 繼續排隊
} else {
$eliminated[] = $monkey;
echo "🐵 猴子 {$monkey} 被淘汰!
";
$k = $m; // 重置計數
}
}
$survivor = $monkeys[0];
echo "
🏆 最後倖存: 猴子 {$survivor} (斯巴達!)";
return $survivor;
}
// 測試
$monkeys = range(1, 10); // 10 隻猴子,編號 1-10
$m = 3; // 數到 3 淘汰
killMonkeys($monkeys, $m);
| 解法 | 時間複雜度 | 空間複雜度 | 測試耗時 | 備註 |
|---|---|---|---|---|
| 陣列模擬 | O(n×m) | O(n) | ~2.3s | array_splice 移動元素慢 |
| 隊列模擬 | O(n×m) | O(n) | ~1.8s | shift/push 較快 |
| 數學遞歸 | O(n) | O(1) | ~0.002s | 極快,推薦大數據 |
| 遞歸函數 | O(n) | O(n) 棧 | ~0.003s | n>10000 會棧溢出 |
<?php
function josephus_full_order($n, $m) {
$people = range(1, $n);
$index = 0;
$order = [];
while (!empty($people)) {
$index = ($index + $m - 1) % count($people);
$order[] = $people[$index];
array_splice($people, $index, 1);
}
return $order; // 最後一個為倖存者
}
// 使用
$order = josephus_full_order(7, 3);
echo "淘汰順序: " . implode(' → ', $order) . "\n";
// 輸出: 3 → 6 → 2 → 7 → 5 → 1 → 4 (4 為倖存者)
遞歸公式 f(n,m) = (f(n-1,m) + m) % n 推導直觀:
(m-1)%n 的人m%nf(n-1,m),映射回原陣列需加上偏移 m 並取模 n約瑟夫環展示了從「模擬思維」到「數學思維」的算法優化過程。實際工程中,理解問題的數學本質往往能帶來指數級效能提升。