| 算法 | 時間複雜度 | 空間複雜度 | 適用範圍 | 備註 |
| 集合去重循環 | O(N²) 最壞 | O(N) | N << 範圍 | 範圍小時極慢 |
| Fisher-Yates 洗牌 | O(範圍) | O(範圍) | 範圍較小(<100萬) | 生成全排列後取前 N |
| 水塘抽樣 | O(N) | O(N) | 任意範圍、大 N | 推薦:單次遍歷、等概率 |
| 隨機集合 + 衝突重試 | O(N) 期望 | O(N) | N < 範圍/2 | PHP 內建 random_int 高效 |
推薦實現:水塘抽樣(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 個不重複數)
| 範圍 | 算法 | 耗時 | 內存 |
| 1-10000 | range+shuffle | ~2ms | ~400KB |
| 1-1000000 | 水塘抽樣 | ~15ms | ~80KB |
| 1-1000000 | 集合去重循環 | ~8ms | ~80KB |
| 1-100000000 | 水塘抽樣 | ~120ms | ~80KB |
| 1-100000000 | 集合去重 | 極慢/超時 | 高 |
- 上一篇文章: PHP 二維數組排序的多種方法與 array_multisort 深度解析
- 下一篇文章: PHP cURL 多線程併發請求原理與高性能實戰封裝
Copyright ©
程式碼之家 All Rights Reserved