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

PHP 生成 N 個不重複隨機數的高效算法與實現

     編輯:PHP 相關知識

核心需求

在 [min, max] 範圍內生成 N 個**不重複**的隨機整數。常見場景:抽獎、隨機抽樣、驗證碼、分片 ID 分配。

算法對比

推薦實現:水塘抽樣(Reservoir Sampling)

**優點**:不需構建完整範圍數組、內存固定 O(N)、單次遍歷、數學證明等概率。

<?php
/**
 * 生成 N 個不重複隨機整數(水塘抽樣)
 * @param int $min    最小值(含)
 * @param int $max    最大值(含)
 * @param int $count  生成數量
 * @return array
 * @throws InvalidArgumentException
 */
function uniqueRandomInts(int $min, int $max, int $count): array
{
    if ($count < 0) throw new InvalidArgumentException('Count 不能為負');
    if ($min > $max) throw new InvalidArgumentException('Min 不能大於 Max');
    
    $range = $max - $min + 1;
    if ($count > $range) throw new InvalidArgumentException("請求數量($count)超過範圍大小($range)");

    // 小範圍優化:直接洗牌
    if ($range <= 100000) {
        $pool = range($min, $max);
        shuffle($pool);
        return array_slice($pool, 0, $count);
    }

    // 大範圍:水塘抽樣
    $result = [];
    $selected = 0;
    
    for ($i = $min; $i <= $max; $i++) {
        $remaining = $max - $i + 1; // 剩餘未處理數字數
        $needed = $count - $selected; // 還需選取數
        
        // 以 needed/remaining 概率選中當前數字
        if (random_int(1, $remaining) <= $needed) {
            $result[] = $i;
            $selected++;
            if ($selected === $count) break;
        }
    }
    
    // 打亂結果順序(水塘抽樣輸出是有序的)
    shuffle($result);
    return $result;
}

// 使用
$numbers = uniqueRandomInts(1, 10000000, 100); // 1000萬範圍取100個
print_r($numbers);

簡潔版:集合去重(適用 N 較小、範圍不大)

<?php
/**
 * 簡潔版:利用陣列鍵唯一性去重
 */
function uniqueRandomSimple(int $min, int $max, int $count): array
{
    $range = $max - $min + 1;
    if ($count > $range) $count = $range;
    
    $result = [];
    while (count($result) < $count) {
        // 利用鍵去重:重複值會覆蓋,不增加 count
        $result[random_int($min, $max)] = true;
    }
    return array_keys($result);
}

// 更簡潔(PHP 8.1+):array_unique + random_int 陣列
function uniqueRandomModern(int $min, int $max, int $count): array
{
    $range = $max - $min + 1;
    $count = min($count, $range);
    
    // 生成稍多一些再去重,減少循環次數
    $extra = min(10, $count);
    $raw = array_map(fn() => random_int($min, $max), range(1, $count + $extra));
    return array_slice(array_keys(array_flip($raw)), 0, $count);
}

進階:加權隨機不重複抽樣

每個數字有權重,按權重概率抽取 N 個不重複。

<?php
/**
 * 加權不重複隨機抽樣
 * @param array $weights [數字 => 權重]
 * @param int $count
 * @return array
 */
function weightedUniqueRandom(array $weights, int $count): array
{
    $items = array_keys($weights);
    $totalWeight = array_sum($weights);
    $result = [];
    
    while (count($result) < $count && $items) {
        $rand = random_int(1, $totalWeight);
        $accum = 0;
        
        foreach ($items as $i => $item) {
            $accum += $weights[$item];
            if ($rand <= $accum) {
                $result[] = $item;
                $totalWeight -= $weights[$item];
                unset($items[$i], $weights[$item]);
                break;
            }
        }
    }
    return $result;
}

// 使用:號碼 1-10,權重不同
$weights = [1=>10, 2=>20, 3=>30, 4=>15, 5=>25, 6=>10, 7=>5, 8=>5, 9=>5, 10=>5];
print_r(weightedUniqueRandom($weights, 3));

生成隨機字符串(驗證碼、Token、邀請碼)

<?php
/**
 * 生成指定字符集的隨機字符串
 */
function randomString(int $length, string $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'): string
{
    $bytes = random_bytes($length);
    $mask = strlen($charset) - 1;
    $result = '';
    for ($i = 0; $i < $length; $i++) {
        $result .= $charset[$bytes[$i] & $mask];
    }
    return $result;
}

/**
 * 生成不重複的邀請碼(字母數字混合,排除易混淆字符)
 */
function generateInviteCode(int $count, int $length = 8): array
{
    $charset = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ'; // 無 0,O,1,I
    $codes = [];
    while (count($codes) < $count) {
        $codes[randomString($length, $charset)] = true;
    }
    return array_keys($codes);
}

密碼學安全隨機數(關鍵)

  • mt_rand():梅森旋轉,**不可用於安全場景**,可預測
  • random_int():PHP 7+,**CSPRNG**(密碼學安全偽隨機數生成器),**必須用於 Token、驗證碼、加密、抽獎**
  • random_bytes():生成安全隨機字節,用於 Key、IV、Salt
  • openssl_random_pseudo_bytes():OpenSSL 實現,同上
<?php
// ✅ 正確:安全隨機數
$token = bin2hex(random_bytes(32)); // 64 位十六進制
$code  = random_int(100000, 999999); // 6 位數字驗證碼

// ❌ 錯誤:不可預測性不足
$token = md5(mt_rand() . microtime()); // 可被暴力破解
$code  = mt_rand(100000, 999999);

性能測試參考(生成 10000 個不重複數)

算法時間複雜度空間複雜度適用範圍備註
集合去重循環O(N²) 最壞O(N)N << 範圍範圍小時極慢
Fisher-Yates 洗牌O(範圍)O(範圍)範圍較小(<100萬)生成全排列後取前 N
水塘抽樣O(N)O(N)任意範圍、大 N推薦:單次遍歷、等概率
隨機集合 + 衝突重試O(N) 期望O(N)N < 範圍/2PHP 內建 random_int 高效
範圍算法耗時內存
1-10000range+shuffle~2ms~400KB
1-1000000水塘抽樣~15ms~80KB
1-1000000集合去重循環~8ms~80KB
1-100000000水塘抽樣~120ms~80KB
1-100000000集合去重極慢/超時高
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved