HOWTO · C++

在 C++ 中隨機化向量

本文介紹瞭如何在 C++ 中隨機化向量的方法

本頁內容

若要就地隨機重排 C++ 向量中的元素,請將向量的 begin() 和 end() 迭代器,以及一個亂數引擎傳給 std::shuffle。此演算法會重新排列指定範圍內的元素,不會建立另一個向量。若要讓偽隨機選擇序列可重現,請使用固定的引擎種子;若希望不同次執行通常產生不同序列,則請使用適當的來源為引擎設定種子。

使用 std::shuffle 洗牌向量

std::shuffle 宣告於 <algorithm> 標頭檔。前兩個引數界定一個半開迭代器範圍:第一個迭代器指向要納入的第一個元素,第二個迭代器則指向最後一個元素之後的位置。因此,對向量而言,values.begin() 和 values.end() 涵蓋整個向量。第三個引數是均勻隨機位元產生器(UniformRandomBitGenerator),通常是 std::mt19937 之類的引擎。引擎提供用來選擇排列順序的隨機位元;它不是會傳回已洗牌向量的回呼函式。

以下完整範例使用固定種子,因此每次執行程式時,引擎都會從相同狀態開始。std::shuffle 不保證產生特定的洗牌順序:結果可能因標準程式庫的實作而異,因此請將下方輸出視為其中一種範例,而非保證會出現的序列。

執行以下範例:

#include <algorithm>
#include <cstddef>
#include <iostream>
#include <random>
#include <vector>

int main() {
    std::vector<int> values{12, 32, 43, 53, 23, 65, 84};
    std::mt19937 engine(42);

    std::shuffle(values.begin(), values.end(), engine);

    for (std::size_t i = 0; i < values.size(); ++i) {
        if (i != 0) {
            std::cout << ' ';
        }
        std::cout << values[i];
    }
    std::cout << '\n';
}

輸入向量為 12 32 43 53 23 65 84。在提供的成功執行結果中,程式印出了以下洗牌順序:

23 12 43 53 84 65 32

使用固定種子,可讓偽隨機引擎在相同實作與設定下產生可重現的序列。但這不保證所有標準程式庫實作都會輸出相同的洗牌結果,因為演算法的確切選擇順序並未規定為一個通用序列。若希望每次執行通常得到不同結果,請為引擎設定一次種子,之後重複用於洗牌;不要為每個元素或每次呼叫都建立新引擎。std::random_device 常用作種子來源,但其行為取決於實作與執行環境;不應將它描述為保證產生非決定性結果或具備密碼學安全隨機性的來源。

也可以使用 std::begin(values) 和 std::end(values) 表示範圍;這兩個函式可透過標準迭代器工具使用。撰寫可接受不同容器類型或內建陣列的泛型輔助函式時,這種寫法很有用。若只處理向量,使用其成員迭代器會更直接明確。無論採用哪種寫法,兩個端點都必須來自同一個容器,且範圍必須包含預計要重新排列的元素。此操作會就地改變向量順序,因此後續讀取或走訪向量的程式碼會看到洗牌後的排列。

處理使用 std::random_shuffle 的舊程式碼

std::random_shuffle 是較舊的標準程式庫演算法,可能出現在既有的 C++ 程式碼中。它是否可用,取決於專案選用的語言標準及所使用的程式庫實作。維護這類程式碼時,請先確認這些設定,再假設該名稱可用。較舊的多載可能使用由實作定義的隨機來源;接受產生器的多載,對產生器的要求也與 std::shuffle 的引擎引數不同。

撰寫新程式碼或更新既有程式碼時,建議使用 std::shuffle 並傳入明確的亂數引擎。這樣能清楚呈現隨機來源,並可透過種子控制結果是否可重現,也能避免依賴較舊介面。不要因為程式碼中出現 std::random_shuffle,就推斷它在所有編譯模式下都可用;也不要認為所有較舊環境都沒有 std::shuffle。相容性取決於專案的標準程式庫與語言標準設定。若專案必須保留舊程式碼,請將相容性選擇限於該專案,並使用實際建置時採用的編譯器設定加以確認。