隨機演算法

ai-exception發表於2018-05-17

概述

隨機演算法是一種在接受輸入的同時,為了隨機選擇的目的,還接受一串隨機位元流並且在執行過程中使用該位元流的演算法(允許演算法在執行過程中隨機地選擇下一個計算步驟)。

隨機演算法通常有兩個優點

  • 較之那些我們所知的解決同一問題最好的確定性演算法,隨機演算法所需的執行時間或空間通常小一些。
  • 隨機演算法比較易於理解和實現(呵,站著說話不腰疼)。

隨機演算法的基本特徵:對所求解問題的同一例項用同一隨機演算法求解兩次可能得到完全不同的效果。
這兩次求解所需的時間、所得到的結果可能會有相當大的差別。

隨機演算法的類別

隨機(概率)演算法大致分四類:

  • 數值概率演算法

    常用於數值問題的求解。
    這類演算法所得到的往往是近似解且近似解的精度隨計算時間的增加而不斷提高。
    在許多情況下,要計算出問題的精確解是不可能的,或者沒有必要,此時,用數值概率演算法可以得到相當滿意的解。

  • 蒙特卡洛(Monte Carlo)演算法

    用於求問題的準確解。
    對於許多問題來說,近似解毫無意義。
    例如,一個判定問題其解為“是”或“否” ,二者必居其一,
    不存在任何近似解。
    又如,我們要求一個整數的因子,這樣問題的解答必須是準
    確的,一個整數的近似因子沒有任何意義。
    用蒙特卡洛演算法能求得問題的一個解,但這個解未必是正確的。
    求得正確解的概率依賴於演算法所用的時間,演算法所用的時間越多,得到正確解的概率就越高。
    蒙特卡洛演算法的主要缺點也在於此,即無法有效地判定所得到的解是否肯定正確。

  • 拉斯維加斯(Las Vegas)演算法

拉斯維加斯演算法不會得到不正確的解。
一旦用拉斯維加斯演算法找到一個解,這個解就一定是正確
解。
但有時用拉斯維加斯演算法會找不到解。
與蒙特卡洛演算法類似,拉斯維加斯演算法找到正確解的**概率隨著它所用的計算時間
的增加而提高**。
對於所求解問題的任一例項,用拉斯維加斯演算法反覆對該例項求解足夠多次,可使求解失效的概率任意小。

  • 舍伍德(Sherwood)演算法

舍伍德演算法總能求得問題的一個解,且所求得的解總是正確的。
當一個確定性演算法在最壞情況下的計算複雜性與其在平均情況下的計算複雜性有較大差別時,可在這個確定性演算法中引入隨機性,將它改造成一個舍伍德演算法,消除或減少問題的好壞例項間的這種差別。

例子


  • Monte Carlo演算法

總是給出解,但是偶爾可能會產生非正確的解。然而,可以通過多次執行原演算法,並且滿足每次執行時的隨機選擇都相互獨立,使產生非正確解的概率可以減到任意小。

主主元素問題(多數元素問題)

問題描述

設T[1…n]是一個長度為n的陣列,當某個元素在該陣列中存在的數量多於int(s/2)時稱該元素為陣列T的主元素(多數元素)。

求解思路

演算法隨機選擇陣列元素x,由於陣列T的非主元素個數小於n/2,所以,x不為主元素的概率小於1/2。因此判定陣列T的主元素存在性的演算法是一個偏真1/2正確的演算法。50%的錯誤概率是不可容忍的,利用重複呼叫技術將錯誤概率降低到任何可接受的範圍內。對於任何給定的p> 0,演算法majorityMC重複呼叫(向上取整)log(1/p)次演算法majority。它是一個偏真蒙特卡羅演算法,且其錯誤概率小於p。演算法majorityMC所需的計算時間顯然是O(nlog(1/p))。

程式碼實現

//重複k次呼叫演算法Majority
template<class Type>
bool MajorityMC(Type *T,int n,double e)
{
    int k = ceil(log(1/e)/log((float)2));
    for(int i=1; i<=k; i++)
    {
        if(Majority(T,n))
        {
            return true;
        }
    }
    return false;
}


bool Majority(Type *T,int n)
{
    RandomNumber rnd;
    int i = rnd.Random(n);

    Type x = T[i];  //隨機選擇陣列元素
    int k = 0;

    for(int j=0; j<n; j++)
    {
        if(T[j] == x)
        {
            k++;
        }
    }

    return (k>n/2); //k>n/2時,T含有主元素
}

隨機化快速排序

(可能是最為流行的一種隨機演算法?)
首先要明確傳統快速排序的流程:從待排序序列中拿出最後一個(或者第一個)作為主元,將小於它的放在它的前面,大於它的放在它的後面,這時對該主元的前一部分和後一部分再分別遞迴進行“劃分”,最後達到有序。這種方法有一個很大的問題就是當待排序陣列是一個幾乎有序的序列時其複雜度會很容易達到senta(n^2),因為如果每次都選擇第一個元素或者最後一個元素作為主元進行劃分,對一個幾乎有序的序列,劃分後的遞迴物件(子序列)將會一個很長一個很短,這樣可能經過好多次劃分後還是有一個待劃分的子部分很長。解決方法是每次不選擇第一個或者最後一個作為主元,而是隨機產生一個從第一個到最後一個之間的隨機數作為主元進行劃分,這樣即保留了快速排序的優越性又避免了排序幾乎有序序列時的痛點。

核心程式碼

void RandomQuickSort(int low,int high){
    if(low<high){

        int v=random(low,high);
        int t=A[low];
        A[low]=A[v];
        A[v]=t;

        Split(A[low...high],low);
        RandomQuickSort(low,v-1);
        RandomQuickSort(v+1,high);
    }
}

該演算法在最壞情況下仍然是senta(n^2),但這與輸入形式無關。如果最壞情況發生,那是因為用隨機數選取的主元不湊巧,這個事件發生的概率是非常小的。事實上,沒有一種輸入的排列可以引起它的最壞情況,演算法的期望執行時間是senta(nlogn).

隨機化的選擇演算法

什麼是選擇演算法:

SELECT 演算法描述

  1. 如果陣列元素個數小於 44,則直接將陣列排序並返回第 k小元素(採用直接的方法來解決問題,因為當總元素個數小於44*5=220的時候用直接的方法解決問題更快)。
  2. 把 n 個元素以每組 5 個元素劃分為 int( n/5) 組,如果 n 不是 5的倍數則拋棄剩餘元素。
  3. 對每組進行排序,之後取出每組的中間項(第 3 個元素)。
  4. 遞迴呼叫 SELECT 演算法,得到這些中間項序列中的中項元素 mm
  5. 根據 mm,將原陣列 A 劃分為三個子陣列:

    • A1={小於 mm 的元素};
    • A2={等於 mm 的元素};
    • A3={大於 mm 的元素};
  6. 根據 k 的大小,判斷第 k 小元素會出現在 A1,A2,A3 中的
    哪一個陣列裡,之後,或者返回第 k 小元素(mm,在 A2
    中),或者在 A1 或 A3 上遞迴。

    1:k <len(A 1) 第 k 小元素在 A1 中;
    2:len(A 1) =k <= A 1+ A 2 第 k 小元素在 A2 中;
    3: A 1 + A 2 < k <= A 1 + A 2+  A 3第 k 小元素在 A3 中;
    

    該演算法的執行時間是senta(n)(T(n)<=20cn,c是排序43個元素所需的時間),具有一個很大的常數係數,所以就有了隨機版的選擇演算法:

    /*
     * 輸入:n個元素的陣列A[1...n]和整數k
     * 輸出:A中的第k小元素
     * 
     */
    
    R_Select(A,1,n,k);
    
    void R_Select(int *A, int low, int high, int k) {
        int v = random(low, high);
        int x = A[v];
        A1 = {a | a < x};
        A2 = {a | a = x};
        A3 = {a | a > x};
    
        if (A1.len >= k)return R_Select(A, 1, A1.len, k);
        else if (A.len + A2.len >= k)return x;
        else if (A1.len + A2.len < k)return R_Select(A, 1, A3.len, k - A1.len - A2.len);
    }
    

該版本的隨機演算法與原版本的主要不用是原版本是將序列分為若干個5元素序列分別進行排序後找到那些5元素序列中值組成的新序列的中值作為主元對元素進行劃分,而隨機演算法是產生一個序列中的隨機數(就是在待找序列中隨機找了一個數)作為主元,省去了那些排序5元素陣列的步驟,對於大小為n的輸入,演算法RandomizedSekect執行的期望比較次數小於4n,他的期望執行時間是senta(n)。
可以發現該隨機演算法最壞情況下的執行時間也是senta(n),但是其發生最壞情況不依賴於輸入,僅當產生的隨機數序列很不湊巧時才會發生,而這種概率是非常小的。

相關文章