需求:產生 0 到 N-1 的隨機排列,每個數字恰好出現一次。最直覺的「隨機產生 + 檢查重複」在 N 較大時會陷入極端惡化的重試迴圈 (耦合佔用問題)。本文介紹一種原地陣列壓縮法,本質上是 Fisher-Yates 洗牌演算法的記憶體友善變體,時間 O(N)、空間 O(N),無重試、無死循環風險。
result[N] (輸出)、pool[N] (候選池,初始為 0,1,2...N-1)pool[0 ~ N-1-i] 中隨機選一個索引 rpool[r] 複製到 result[i]pool[r] 標記為已用 (如 -1),並將 pool 陣列向前壓縮,移除該空位#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define MAX_VALUE 10
/* 將陣列中標記為 -1 的元素向前壓縮,返回新的有效長度 */
static int compress_pool(int *pool, int size)
{
int write = 0;
for (int read = 0; read < size; ++read) {
if (pool[read] != -1) {
pool[write++] = pool[read];
}
}
return write; // 新的有效長度
}
/* 產生 0..n-1 的隨機排列,存入 result 陣列 */
int rand_array(int *result, int n)
{
if (!result || n <= 0) return -1;
int *pool = malloc(n * sizeof *pool);
if (!pool) return -1;
/* 初始化候選池 */
for (int i = 0; i < n; ++i) pool[i] = i;
int valid = n; // 目前候選池有效長度
for (int i = 0; i < n; ++i) {
int r = rand() % valid; // 0 ~ valid-1
result[i] = pool[r]; // 取出隨機元素
pool[r] = -1; // 標記已用
valid = compress_pool(pool, valid); // 壓縮陣列
}
free(pool);
return 0;
}
int main(void)
{
srand((unsigned)time(NULL)); // 真實專案請用更強隨機源
int list[MAX_VALUE];
if (rand_array(list, MAX_VALUE) == 0) {
for (int i = 0; i < MAX_VALUE; ++i)
printf("list[%d] = %d\n", i, list[i]);
}
return 0;
}
a[i] ↔ a[r],最省記憶體。本文的「雙陣列 + 壓縮」寫法較易理解「從候選池抽取」的語意,適合教學展示;生產環境建議改用單陣列交換版。srand(time(NULL)),同一秒內多次執行會產生相同序列。生產環境請採用:getrandom() / /dev/urandomBCryptGenRandom() / RtlGenRandom()std::random_device + std::mt19937void fisher_yates_shuffle(int *arr, int n)
{
for (int i = n - 1; i > 0; --i) {
int j = rand() % (i + 1); // 0 ~ i
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
/* 使用方式 */
int main(void)
{
int a[MAX_VALUE];
for (int i = 0; i < MAX_VALUE; ++i) a[i] = i;
fisher_yates_shuffle(a, MAX_VALUE);
// a 即為隨機排列
}
| 演算法 | 時間複雜度 | 空間複雜度 | 實測耗時 (參考) |
|---|---|---|---|
| 隨機產生 + 重試檢查 | O(N²) 期望,最壞無窮 | O(N) | > 60 秒 (常死循環) |
| 雙陣列壓縮法 (本文) | O(N²) | O(N) | ~1.2 秒 |
| Fisher-Yates 原地交換 | O(N) | O(1) 額外 | ~3 ms |
rand() 完全不夠,必須用 CSPRNGrand() 非執行緒安全,改用 rand_r() 或 thread-local RNG核心洞見:隨機排列的本質是「從遞減的候選集合中均勻取樣」。Fisher-Yates 用「交換到尾端」隱性實現候選集合縮減,不需額外陣列、不需壓縮,是理論最優解。