1. 程式人生 > >隨機演算法 Las Vegas演算法 Monte Carlo演算法

隨機演算法 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;
}
}