首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 解決約瑟夫環問題:經典算法實作與多種解法對比

     編輯:PHP 相關知識

約瑟夫問題 (Josephus Problem) 是經典的計算機科學與數學問題:N 個人圍成圈,每數到 M 個人淘汰一個,求最後倖存者編號。本文提供 PHP 多種解法實作與效能分析。

問題定義

  • 輸入:總人數 n、步長 m
  • 規則:從 1 開始報數,報到 m 的人出局,下一人從 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];
}

解法三:數學遞歸法 (最佳效能 O(n) 時間、O(1) 空間) ✅

<?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);

效能對比測試 (n=100,000, m=3)

解法時間複雜度空間複雜度測試耗時備註
陣列模擬O(n×m)O(n)~2.3sarray_splice 移動元素慢
隊列模擬O(n×m)O(n)~1.8sshift/push 較快
數學遞歸O(n)O(1)~0.002s極快,推薦大數據
遞歸函數O(n)O(n) 棧~0.003sn>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 為倖存者)

應用場景擴展

  • 負載均衡:輪詢調度中移除故障節點
  • 遊戲開發:回合制淘汰、圓桌騎士選拔
  • 分散式系統:Leader 選舉、Token Ring 協議
  • 密碼學:某些流密鑰生成算法相關

數學推導補充

遞歸公式 f(n,m) = (f(n-1,m) + m) % n 推導直觀:

  1. n=1 時,唯一倖存者索引為 0
  2. n 人時,第一輪淘汰索引 (m-1)%n 的人
  3. 剩下 n-1 人,但起始位置偏移了 m%n
  4. 子問題解為 f(n-1,m),映射回原陣列需加上偏移 m 並取模 n

約瑟夫環展示了從「模擬思維」到「數學思維」的算法優化過程。實際工程中,理解問題的數學本質往往能帶來指數級效能提升。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved