隨機演算法 Las Vegas演算法 Monte Carlo演算法
隨機演算法
定義
- 不要求對所有輸入均正確計算,只要求出現錯誤的可能性小到可忽略(得能解決問題)
- 同一組輸入,不要求同一個結果(不確定)
應用
有些使用確定性求解演算法效率會很差的問題,如果用隨機演算法求解,可以很快得到相當可信的結果,典型用於公鑰,RSA演算法。
分類
主要分為LasVegas演算法和MonteCarlo演算法
Las Vegas演算法
- 少數應用時會出現求不出解的情況
- 但一旦找到一個解,則一定是正確的
- 求不出解的時候需再次呼叫演算法計算,直到獲得解
- 對於此類演算法,主要是分析演算法的時間複雜度的期望值,以及呼叫一次產生失敗的概率
Monto Carlo演算法
-
通常不能保證計算出來的結果總是正確的, 一般只能斷定所給解的正確性不小於p ( 1/2<p<1)
-
通過演算法的反覆執行(即以增大演算法的執行時間為代價),能夠使發生錯誤的概率 小到可以忽略的程度 (越算越好)
-
由於每次執行的演算法是獨立的,故k次執行均發生錯誤的概率為(1-p)k
-
對於判定問題(回答只能是“Yes”或 “No”)
- 帶雙錯的(two-sided error): 回答”Yes”或”No”都 有可能錯
- 帶單錯的(one-sided error):只有一種回答可能錯
-
Las Vegas演算法可以看成是單錯概率為0的 Monte Carlo演算法
優點
- 對於某一給定的問題,隨機演算法所需的時 間與空間複雜性,往往比當前已知的確定性演算法要好
- 到目前為止設計出來的各種隨機演算法,無 論是從理解上還是實現上,都是極為簡單的
- 隨機演算法避免了去構造最壞情況的例子
具體問題分析及實現
找第k小元素的隨機演算法 (Las Vegas演算法)
- 在n個數中隨機的找一個數A[i]=x, 然後將 其餘n-1個數與x比較,分別放入三個陣列中S1(元素均<x), S2(元素均=x), S3(元素 均>x)
- 若|S1|≥k ,則呼叫Select(k,S1)
- 若(|S1|+|S2|)≥k,則第k小元素就是x
- 否則就有(|S1|+|S2|)< k,此時呼叫 Select(k-|S1|-|S2|,S3)
package LasVegas;
import java.util.*;
public class NumKSmall {
public static void main(String[] args) {
// TODO 自動生成的方法存根
Random r = new Random(1);
ArrayList<Integer> x = new ArrayList<Integer>();
for(int i = 0; i < 100; i++) {
x.add(r.nextInt(100));
}
int k = r.nextInt(100);
System.out.println("在x中第 %d 小的數字是: ",k);
System.out.println(NKS_LV(x, k));
}
private static int NKS_LV(ArrayList<Integer> x, int k) {
// TODO 自動生成的方法存根
int len = x.size();
Random r = new Random(1);
int s = x.get(r.nextInt(len));
ArrayList<Integer> S1 = new ArrayList<Integer>();
ArrayList<Integer> S2 = new ArrayList<Integer>();
ArrayList<Integer> S3 = new ArrayList<Integer>();
for(int i = 0; i < len; i++) {
int xx = x.get(i);
if (xx < s) {
S1.add(xx);
}else if (xx == s){
S2.add(xx);
}else {
S3.add(xx);
}
}
if (k <= S1.size()) {
return NKS_LV(S1,k);
}else if ( k <= S1.size() + S2.size()) {
return s;
}else {
return NKS_LV(S3, k - S1.size() - S2.size());
}
}
}
其實經典的BFPTR演算法就是改進了隨機的部分,而是採用將array分成5個部分,找出5個部分中位陣列成的陣列的中位數作為最初的s,可以保證**O(N)**的時間複雜度。
用到的思想就是LasVegas或者說Sherwood隨機化方法,消除或減少問題的好壞輸入例項之間的差別。
測試字串是否相同(Monte Carlo演算法)
設A處有一個長字串x(e.g. 長度為106), B處也有一個長字串y,A將x發給B,由 B判斷是否有x=y。
- 首先由A發一個x的長度給B,若長 度不等,則x≠y
- 若長度相等,則採用“取指紋”的方法:
- A對x進行處理,取出x的“指紋”,然後將x的“指紋” 發給B
- 由B檢查x的“指紋”是否等於y 的“指紋”
- 若取k次“指紋”(每次取法不同),每次兩者結果均相同,則認為x與y是相等的
- 隨著k的增大,誤判率可趨於0
思路是這樣,java中hashcode的思路就是這樣,取模就是hash,equals判斷的時候就是利用雜湊,雜湊中取的是31。如果多次取不同的k,判斷多次兩者指紋都一樣,MC演算法就認定x=y。
//String裡的hashCode()
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
錯判率分析
B接到指紋Ip(x)後與Ip(y)比較 如果Ip(x)≠Ip(y),當然有x≠y
如果Ip(x)=Ip(y)而x≠y,則稱此種情況為一個誤匹配
現在需要確定:誤匹配的概率有多大?
若總是隨機地去取一個小於M的素數p,則對於給定 的x和y,
Pr[failure] =(使得Ip(x)=Ip(y)但x≠y的素數 p(p<M)的個數)/(小於M的素數的總個數)
Pattern Matching (Monto Carlo演算法)
問題
給定兩個字串:X=x1,…,xn,Y=y1,…,ym, 看Y是否為X的子串?(即Y是否為X中的 一段)
- KMP演算法剛總結過,確實麻煩,隨機演算法可以大大簡化思考的難度。brute-force思想
- 記X(j)=xj x j+1…x j+m-1(從X的第j位開始、 長度與Y一樣的子串)
- 從起始位置j=1開始到 j=n-m+1,不去逐一 比較X(j)與Y,而僅逐一比較X(j)的指紋 Ip(X(j))與Y的指紋Ip(Y)
- 由於Ip(X(j+1))可以很方便地根據Ip (X(j))計算出來,故演算法可以很快完成
1.隨機取一個小於M的素數p,置j←1;
2.計算Ip(Y)、Ip(X(1))及Wp(=2m mod p);
3.While j≤n-m+1
do
{
if Ip(X(j))=Ip(Y) then return j /﹡X(j)極有可能等於Y﹡/
else{
根據Ip(X(j))計算出Ip(X(j+1));j增1
}
}
4.return 0; /﹡X肯定沒有子串等於Y﹡/
時間複雜度分析
- 計算Ip(Y)、Ip(X(1))及2m mod p的時間不 超過O(m)次運算
- Ip(X(j+1))的計算,只需用O(1)時間
- 由於迴圈最多執行n-m+1次,故這部分的 時間複雜度為O(n),於是,總的時間複雜 性為O(m+n)
錯判率分析 - 當Y≠X(j),但Ip(Y)=Ip(X(j))時產生失敗
- 失敗的概率Pr[failure]<1/n 【這裡有點迷惑,為什麼是<1/n,不應該是<1/m麼?每次比的時候是按Y的長度比的啊】,即失敗的概率 只與X的長度有關,與Y的長度無關
上面兩個問題的應用可以理解為:如果指紋不等,則必不存在;如果指紋相等,則大概率存在。在演算法中判定為成功。具體程式碼沒有編寫,這種問題多看些資料擴充套件思路很有必要,我通過學習這些不斷查詢的名詞和資料路徑:
Brute Force
Pattern Matching
串匹配 - 中國科學技術大學
指紋函式
模和環(矩陣論,矩陣做f的數學基礎需要補足,之前fibonacci的時候那個f就遇到過,本質是一種聰明的對映,需要掌握方法)
其中每個部分深入理解都大有獲益。下次遇到相關問題時再進行總結。
- Random Sampling問題
主元素問題
設T[1:n]是一個含有n個元素的陣列。當 |{i|T[i]=x}|>n/2時,稱元素x是陣列T的主元素
問題描述 :對於給定的陣列T,判定T陣列中是否含有主元素
Monte Carlo演算法:
package LasVegas;
import java.util.*;
public class NumKSmall {
public static void main(String[] args) {
// TODO 自動生成的方法存根
Random r = new Random(1);
ArrayList<Integer> x = new ArrayList<Integer>();
for(int i = 0; i < 100; i++) {
x.add(r.nextInt(100));
}
int k = r.nextInt(100);
System.out.printf("在x中第 %d 小的數字是: \n",k);
System.out.println(NKS_LV(x, k));
}
private static int NKS_LV(ArrayList<Integer> x, int k) {
// TODO 自動生成的方法存根
int len = x.size();
Random r = new Random(1);
int s = x.get(r.nextInt(len));
ArrayList<Integer> S1 = new ArrayList<Integer>();
ArrayList<Integer> S2 = new ArrayList<Integer>();
ArrayList<Integer> S3 = new ArrayList<Integer>();
for(int i = 0; i < len; i++) {
int xx = x.get(i);
if (xx < s) {
S1.add(xx);
}else if (xx == s){
S2.add(xx);
}else {
S3.add(xx);
}
}
if (k <= S1.size()) {
return NKS_LV(S1,k);
}else if ( k <= S1.size() + S2.size()) {
return s;
}else {
return NKS_LV(S3, k - S1.size() - S2.size());
}
}
}
n後問題
- 在n×n格的棋盤上放置彼此不受攻擊的n 個皇后。
- 按照國際象棋的規則,皇后可以攻擊與之處在同一 行或同一列或同一斜線上的棋子。n後問題等價於 在n×n格的棋盤上放置n個皇后,任何2個皇后不放 在同一行或同一列或同一斜線上。
- n後問題的Las Vegas演算法思路:
- 各行隨機放置皇后,使新放的與已有的互不攻擊,
- until (n皇后放好||無可供下一皇后放置的位置)
以八皇后為例
package LasVegas;
import java.util.*;
import java.util.Random;
public class NQueens {
static int n;
static ArrayList<Integer> queens;
public static void main(String[] args) {
n = 8;
boolean t = false;
while( t != true) {
t = FindOne(n);
}
if(t == true) {
System.out.println(queens);
}
}
private static boolean FindOne(int nn) {
// TODO 自動生成的方法存根
queens= new ArrayList<Integer>();
int count = 0;
Random r = new Random();
for(int i = 1; i < 9; i++) {
while(queens.size() < i) {
int a = r.nextInt(8) + 1;
int b =r.nextInt(8) + 1;
int j = a*10 + b;
count++;
if (notConflict(j)) {
queens.add(j);
count = 0;
}
if( count == 8) {
System.out.println("演算法失敗");
return false;
}
}
}
return true;
}
private static boolean notConflict(int j) {
// TODO 自動生成的方法存根
for(int q : queens) {
if(j%10 == q%10 || j/10 == q/10 || j/10 - j%10 == q/10 - q%10 || j/10 + j%10 == q/10 + q%10) {
return false;
}
}
return true;
}
}