0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

簡述游戲中常用的兩種隨機(jī)算法(下)

jf_78858299 ? 來源:labuladong ? 作者:labuladong ? 2023-04-12 11:43 ? 次閱讀

先說結(jié)論,當(dāng)你遇到第i個元素時,應(yīng)該有1/i的概率選擇該元素,1 - 1/i的概率保持原有的選擇 ??创a容易理解這個思路:

/* 返回鏈表中一個隨機(jī)節(jié)點(diǎn)的值 */
int getRandom(ListNode head) {
    Random r = new Random();
    int i = 0, res = 0;
    ListNode p = head;
    // while 循環(huán)遍歷鏈表
    while (p != null) {
        i++;
        // 生成一個 [0, i) 之間的整數(shù)
        // 這個整數(shù)等于 0 的概率就是 1/i
        if (0 == r.nextInt(i)) {
            res = p.val;
        }
        p = p.next;
    }
    return res;
}

對于概率算法,代碼往往都是很淺顯的,但是這種問題的關(guān)鍵在于證明,你的算法為什么是對的?為什么每次以1/i的概率更新結(jié)果就可以保證結(jié)果是平均隨機(jī)的?

我們來證明一下,假設(shè)總共有n個元素,我們要的隨機(jī)性無非就是每個元素被選擇的概率都是1/n對吧,那么對于第i個元素,它被選擇的概率就是:

圖片

i個元素被選擇的概率是1/i,在第i+1次不被替換的概率是1 - 1/(i+1),在第i+2次不被替換的概率是1 - 1/(i+2),以此類推,相乘的結(jié)果是第i個元素最終被選中的概率,也就是1/n。因此,該算法的邏輯是正確的。

同理,如果要在單鏈表中隨機(jī)選擇k個數(shù),只要在第i個元素處以k/i的概率選擇該元素,以1 - k/i的概率保持原有選擇即可 。代碼如下:

/* 返回鏈表中 k 個隨機(jī)節(jié)點(diǎn)的值 */
int[] getRandom(ListNode head, int k) {
    Random r = new Random();
    int[] res = new int[k];
    ListNode p = head;

    // 前 k 個元素先默認(rèn)選上
    for (int i = 0; i < k && p != null; i++) {
        res[i] = p.val;
        p = p.next;
    }

    int i = k;
    // while 循環(huán)遍歷鏈表
    while (p != null) {
        i++;
        // 生成一個 [0, i) 之間的整數(shù)
        int j = r.nextInt(i);
        // 這個整數(shù)小于 k 的概率就是 k/i
        if (j < k) {
            res[j] = p.val;
        }
        p = p.next;
    }
    return res;
}

對于數(shù)學(xué)證明,和上面區(qū)別不大:

圖片

雖然每次更新選擇的概率增大了k倍,但是選到具體第i個元素的概率還是要乘1/k,也就回到了上一個推導(dǎo)。

類似的,回到掃雷游戲的隨機(jī)初始化問題,我們可以寫一個這樣的sample抽樣函數(shù):

// 在區(qū)間 [lo, hi) 中隨機(jī)抽取 k 個數(shù)字
int[] sample(int lo, int hi, int k) {
    Random r = new Random();
    int[] res = new int[k];

    // 前 k 個元素先默認(rèn)選上
    for (int i = 0; i < k; i++) {
        res[i] = lo + i;
    }

    int i = k;
    // while 循環(huán)遍歷數(shù)字區(qū)間
    while (i < hi - lo) {
        i++;
        // 生成一個 [0, i) 之間的整數(shù)
        int j = r.nextInt(i);
        // 這個整數(shù)小于 k 的概率就是 k/i
        if (j < k) {
            res[j] = lo + i - 1;
        }
    }
    return res;
}

這個函數(shù)能夠在一定的區(qū)間內(nèi)隨機(jī)選擇k個數(shù)字,確保抽樣結(jié)果是均勻隨機(jī)的且只需要 O(N) 的時間復(fù)雜度。

蒙特卡洛驗(yàn)證法

上面講到的洗牌算法和水塘抽樣算法都屬于隨機(jī)概率算法,雖然從數(shù)學(xué)上推導(dǎo)上可以證明算法的思路是正確的,但如果你筆誤寫出 bug,就會導(dǎo)致概率上的不均等。更神奇的是,力扣的判題機(jī)制能夠檢測出這種概率錯誤。

那么最后我就來介紹一種方法檢測隨機(jī)算法的正確性:蒙特卡洛方法。我猜測力扣的判題系統(tǒng)也是利用這個方法來判斷隨機(jī)算法的正確性的。

記得高中有道數(shù)學(xué)題:往一個正方形里面隨機(jī)打點(diǎn),這個正方形里緊貼著一個圓,告訴你打點(diǎn)的總數(shù)和落在圓里的點(diǎn)的數(shù)量,讓你計(jì)算圓周率。

圖片

這其實(shí)就是利用了蒙特卡羅方法:當(dāng)打的點(diǎn)足夠多的時候,點(diǎn)的數(shù)量就可以近似代表圖形的面積。結(jié)合面積公式,可以很容易通過正方形和圓中點(diǎn)的數(shù)量比值推出圓周率的。

當(dāng)然,打的點(diǎn)越多,算出的圓周率越準(zhǔn)確,充分體現(xiàn)了大力出奇跡的道理。

比如,我們可以這樣檢驗(yàn)水塘抽樣算法sample函數(shù)的正確性:

public static void main(String[] args) {
    // 在 [12, 22) 中隨機(jī)選 3 個數(shù)
    int lo = 12, hi = 22, k = 3;
    // 記錄每個元素被選中的次數(shù)
    int[] count = new int[hi - lo];
    // 重復(fù) 10 萬次
    int N = 1000000;
    for (int i = 0; i < N; i++) {
        int[] res = sample(lo, hi, k);
        for (int elem : res) {
            // 對隨機(jī)選取的元素進(jìn)行記錄
            count[elem - lo]++;
        }
    }
    System.out.println(Arrays.toString(count));
}

這段代碼的輸出如下:

[300821, 299598, 299792, 299198, 299510, 300789, 300022, 300326, 299362, 300582]

當(dāng)然你可以做更細(xì)致的檢查,不過粗略看看,各個元素被選中的次數(shù)大致是相同的,這個算法實(shí)現(xiàn)的應(yīng)該沒啥問題。

對于洗牌算法中的shuffle函數(shù)也可以采取類似的驗(yàn)證方法,我們可以跟蹤某一個元素x被打亂后的索引位置,如果x落在各個索引的次數(shù)基本相同,則說明算法正確,你可以自己嘗試實(shí)現(xiàn),我就不貼代碼驗(yàn)證了。

拓展延伸

到這里,常見的隨機(jī)算法就講完了,簡單總結(jié)下吧。

洗牌算法主要用于打亂數(shù)組,比如我們在 快速排序詳解及運(yùn)用中就用到了洗牌算法保證快速排序的效率。

水塘抽樣算法的運(yùn)用更加廣泛,可以在序列中隨機(jī)選擇若干元素,且能保證每個元素被選中的概率均等。

對于這些隨機(jī)概率算法,我們可以用蒙特卡洛方法檢驗(yàn)其正確性。

最后留幾個拓展題目:

1、本文開頭講到了將二維數(shù)組坐標(biāo)(x, y)轉(zhuǎn)化成一維數(shù)組索引的技巧,那么你是否有辦法把三維坐標(biāo)(x, y, z)轉(zhuǎn)化成一維數(shù)組的索引呢?

2、如何對帶有權(quán)重的樣本進(jìn)行加權(quán)隨機(jī)抽取?比如給你一個數(shù)組w,每個元素w[i]代表權(quán)重,請你寫一個算法,按照權(quán)重隨機(jī)抽取索引。比如w = [1,99],算法抽到索引 0 的概率是 1%,抽到索引 1 的概率是 99%。

3、實(shí)現(xiàn)一個生成器類,構(gòu)造函數(shù)傳入一個很長的數(shù)組,請你實(shí)現(xiàn)randomGet方法,每次調(diào)用隨機(jī)返回?cái)?shù)組中的一個元素,多次調(diào)用不能重復(fù)返回相同索引的元素。要求不能對該數(shù)組進(jìn)行任何形式的修改,且操作的時間復(fù)雜度是 O(1)

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 算法
    +關(guān)注

    關(guān)注

    23

    文章

    4587

    瀏覽量

    92501
  • 游戲
    +關(guān)注

    關(guān)注

    2

    文章

    733

    瀏覽量

    26261
  • 隨機(jī)
    +關(guān)注

    關(guān)注

    0

    文章

    12

    瀏覽量

    9725
收藏 人收藏

    評論

    相關(guān)推薦

    簡述兩種示波器測量眼圖的差別

    簡述兩種示波器測量眼圖的差別 中心議題: 力科示波器進(jìn)行眼圖測量 新舊款軟件包使用方法不同 力科示波器捕獲了50MS的數(shù)據(jù),并一次性地
    發(fā)表于 04-21 16:52 ?3888次閱讀
    <b class='flag-5'>簡述</b><b class='flag-5'>兩種</b>示波器測量眼圖的差別

    兩種典型的ADRC算法介紹

    前言??上篇中詳細(xì)闡述了經(jīng)典的自抗擾控制算法的原理,本篇將圍繞兩種ADRC算法展開,針對擴(kuò)張狀態(tài)觀測器的參數(shù)整定問題進(jìn)行詳解,同時,對跟蹤微分器的幾個重要應(yīng)用進(jìn)行介紹。兩種典型的ADR
    發(fā)表于 09-07 08:02

    智能插座常用兩種通信協(xié)議是什么?

    智能插座常用兩種通信協(xié)議是什么?
    發(fā)表于 09-26 09:18

    c語言常用算法

    非常實(shí)用的《c語言常用算法程序集》針對工程中常用的行之有效的算法而編寫,其主要內(nèi)容包括多項(xiàng)式的計(jì)算、復(fù)數(shù)運(yùn)算、隨機(jī)數(shù)的產(chǎn)生、矩陣運(yùn)算、矩陣特
    發(fā)表于 04-11 16:41

    網(wǎng)絡(luò)中常用的隊(duì)列管理方法比較

    本文主要介紹了網(wǎng)絡(luò)中常用兩種隊(duì)列管理方法:先進(jìn)先出(FIFO)和隨機(jī)提前檢測(RED),并且通過實(shí)驗(yàn)比較了這兩種隊(duì)列管理方法在解決網(wǎng)絡(luò)擁塞控制方面的表現(xiàn),體現(xiàn)了研究
    發(fā)表于 05-25 11:24 ?9次下載

    基于游戲中NPC路徑規(guī)劃的混合算法

    路徑規(guī)劃是游戲人工智能領(lǐng)域的核心問題,如何建立一高效的路徑規(guī)劃方法仍是研究的熱點(diǎn)之一。針對游戲中NPC的路徑規(guī)劃問題,將A*算法與改進(jìn)的人工勢場法相結(jié)合,提出了一
    發(fā)表于 11-14 14:55 ?7次下載

    帕塞瓦定理的兩種常見形式

    帕塞瓦定理的兩種常見形式, 在我的《隨機(jī)信號分析》里面作為附錄4, 即帕塞瓦定理的兩種常見形式, 第三形式即不常用的形式, 明天再給讀者介
    的頭像 發(fā)表于 04-02 11:13 ?9748次閱讀

    Wincc如何與PLC進(jìn)行通訊兩種常用的方式介紹

    西門子WINCC與SiemensPLC通訊連接有多種方式,下面介紹兩種常用的通訊方式。
    的頭像 發(fā)表于 02-17 09:27 ?3w次閱讀
    Wincc如何與PLC進(jìn)行通訊<b class='flag-5'>兩種</b><b class='flag-5'>常用</b>的方式介紹

    單片機(jī)常用兩種延時控制方式

    單片機(jī)中常用的延時控制方式有兩種。一是采用編程的方式達(dá)到延時的目的,另一方法則是通過單片機(jī)中的個定時器T0和T1進(jìn)行計(jì)時達(dá)到延時的目的
    發(fā)表于 07-17 10:22 ?5838次閱讀
    單片機(jī)<b class='flag-5'>常用</b>的<b class='flag-5'>兩種</b>延時控制方式

    常用的hdl語言有哪兩種

    Verilog HDL和VHDL是目前兩種常用的硬件描述語言,同時也都是IEEE標(biāo)準(zhǔn)化的HDL語言。
    發(fā)表于 08-25 09:14 ?9185次閱讀

    說透游戲中常用兩種隨機(jī)算法

    這些 2D 游戲相較現(xiàn)在的大型 3D 游戲雖然看起來有些簡陋,但依然用到很多有趣算法技巧,本文就來深入研究一地圖的隨機(jī)生成
    的頭像 發(fā)表于 11-09 11:17 ?1068次閱讀

    詳解PMSM中常用兩種坐標(biāo)變換

    期介紹了Clarke的Park變化的基本原理,但是經(jīng)過這兩種變換后會存在兩種系數(shù),相信大家都很迷惑,這是什么原因? 主要原因是存在兩種遵循的方式:1、變換前后電流所產(chǎn)生的旋轉(zhuǎn)磁場等
    的頭像 發(fā)表于 01-19 15:52 ?2306次閱讀
    詳解PMSM<b class='flag-5'>中常用</b>的<b class='flag-5'>兩種</b>坐標(biāo)變換

    簡述游戲中常用兩種隨機(jī)算法(上)

    沒事兒的時候我喜歡玩玩那些經(jīng)典的 2D 網(wǎng)頁小游戲,我發(fā)現(xiàn)很多游戲都要涉及地圖的隨機(jī)生成,比如掃雷游戲中地雷的位置應(yīng)該是隨機(jī)分布的:
    的頭像 發(fā)表于 04-12 11:43 ?660次閱讀
    <b class='flag-5'>簡述</b><b class='flag-5'>游戲中常用</b>的<b class='flag-5'>兩種</b><b class='flag-5'>隨機(jī)</b><b class='flag-5'>算法</b>(上)

    基于Python實(shí)現(xiàn)隨機(jī)森林算法

    機(jī)器學(xué)習(xí)算法是數(shù)據(jù)挖掘、數(shù)據(jù)能力分析和數(shù)學(xué)建模必不可少的一部分,而隨機(jī)森林算法和決策樹算法是其中較為常用
    的頭像 發(fā)表于 09-21 11:17 ?1141次閱讀
    基于Python實(shí)現(xiàn)<b class='flag-5'>隨機(jī)</b>森林<b class='flag-5'>算法</b>

    PCBA加工中常見的兩種焊接方式詳解

    ,在PCBA行業(yè)中經(jīng)常被使用。接下來深圳PCBA加工廠家為大家詳細(xì)介紹PCBA加工手工焊接的兩種方式,為您揭秘行業(yè)內(nèi)的技術(shù)細(xì)節(jié)。 PCBA加工過程中常用焊接方式 第一方式是傳統(tǒng)手工焊接。這種方式主要依靠技術(shù)工人的手動操作進(jìn)行焊
    的頭像 發(fā)表于 06-14 09:18 ?471次閱讀