1. 程式人生 > >0049演算法筆記——【隨機化演算法】蒙特卡羅演算法,主元素問題,素數測試問題

0049演算法筆記——【隨機化演算法】蒙特卡羅演算法,主元素問題,素數測試問題

      1、蒙特卡羅演算法

基本概述

蒙特卡羅(Monte Carlo)方法,又稱隨機抽樣或統計試驗方法。傳統的經驗方法由於不能逼近真實的物理過程,很難得到滿意的結果,而蒙特卡羅方法由於能夠真實地模擬實際物理過程,故解決問題與實際非常符合,可以得到很圓滿的結果。

      在實際應用中常會遇到一些問題,不論採用確定性演算法或隨機化演算法都無法保證每次都能得到正確的解答。蒙特卡羅演算法則在一般情況下可以保證對問題的所有例項都以高概率給出正確解,但是通常無法判定一個具體解是否正確
     設p是一個實數,且1/2<p<1。如果一個蒙特卡羅演算法對於問題的任一例項得到正確解的概率不小於p,則稱該蒙特卡羅演算法是p正確的,且稱p-1/2是該演算法的優勢。


如果對於同一例項,蒙特卡羅演算法不會給出2個不同的正確解答,則稱該蒙特卡羅演算法是一致的。
     有些蒙特卡羅演算法除了具有描述問題例項的輸入引數外,還具有描述錯誤解可接受概率的引數。這類演算法的計算時間複雜性通常由問題的例項規模以及錯誤解可接受概率的函式來描述。

原理思想

      當所要求解的問題是某種事件出現的概率,或者是某個隨機變數的期望值時,它們可以通過某種“試驗”的方法,得到這種事件出現的頻率,或者這個隨機變數的平均值,並用它們作為問題的解。這就是蒙特卡羅方法的基本思想。蒙特卡羅方法通過抓住事物運動的幾何數量和幾何特徵,利用數學方法來加以模擬,即進行一種數字模擬實驗。它是以一個概率模型為基礎,按照這個模型所描繪的過程,通過模擬實驗的結果,作為問題的近似解。

主要步驟

     蒙特卡羅解題歸結為三個主要步驟:構造或描述概率過程;實現從已知概率分佈抽樣;建立各種估計量。

 1)構造或描述概率過程: 對於本身就具有隨機性質的問題,如粒子輸運問題,主要是正確描述和模擬這個概率過程,對於本來不是隨機性質的確定性問題,比如計算定積分,就必須事先構造一個人為的概率過程,它的某些參量正好是所要求問題的解。即要將不具有隨機性質的問題轉化為隨機性質的問題。

 2)實現從已知概率分佈抽樣: 構造了概率模型以後,由於各種概率模型都可以看作是由各種各樣的概率分佈構成的,因此產生已知概率分佈的隨機變數(或隨機向量),就成為實現蒙特卡羅方法模擬實驗的基本手段,這也是蒙特卡羅方法被稱為隨機抽樣的原因。最簡單、最基本、最重要的一個概率分佈是(0,1)上的均勻分佈(或稱矩形分佈)。隨機數就是具有這種均勻分佈的隨機變數。隨機數序列就是具有這種分佈的總體的一個簡單子樣,也就是一個具有這種分佈的相互獨立的隨機變數序列。產生隨機數的問題,就是從這個分佈的抽樣問題。在計算機上,可以用物理方法產生隨機數,但價格昂貴,不能重複,使用不便。另一種方法是用數學遞推公式產生。這樣產生的序列,與真正的隨機數序列不同,所以稱為偽隨機數,或偽隨機數序列。不過,經過多種統計檢驗表明,它與真正的隨機數,或隨機數序列具有相近的性質,因此可把它作為真正的隨機數來使用。由已知分佈隨機抽樣有各種方法,與從(0,1)上均勻分佈抽樣不同,這些方法都是藉助於隨機序列來實現的,也就是說,都是以產生隨機數為前提的。由此可見,隨機數是我們實現蒙特卡羅模擬的基本工具。 建立各種估計量: 一般說來,構造了概率模型並能從中抽樣後,即實現模擬實驗後,我們就要確定一個隨機變數,作為所要求的問題的解,我們稱它為無偏估計。  


3)建立各種估計量:相當於對模擬實驗的結果進行考察和登記,從中得到問題的解。 例如:檢驗產品的正品率問題,我們可以用1表示正品,0表示次品,於是對每個產品檢驗可以定義如下的隨機變數Ti,作為正品率的估計量: 於是,在N次實驗後,正品個數為: 顯然,正品率p為: 不難看出,Ti為無偏估計。當然,還可以引入其它型別的估計,如最大似然估計,漸進有偏估計等。但是,在蒙特卡羅計算中,使用最多的是無偏估計。 用比較抽象的概率語言描述蒙特卡羅方法解題的手續如下:構造一個概率空間(W ,A,P),其中,W 是一個事件集合,A是集合W 的子集的s 體,P是在A上建立的某個概率測度;在這個概率空間中,選取一個隨機變數q (w ),w Î W ,使得這個隨機變數的期望值 正好是所要求的解Q ,然後用q (w )的簡單子樣的算術平均值作為Q 的近似值。

      2、主元素問題

問題描述

設T[1:n]是一個含有n個元素的陣列。當|{i|T[i]=x}|>n/2時,稱元素x是陣列T的主元素。例如:陣列T[]={5,5,5,5,5,5,1,3,4,6}中,元素T[0:5]為陣列T[]的主元素。

問題求解

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

     演算法具體程式碼如下:

[cpp] view plaincopyprint?
  1. //隨機化演算法 蒙特卡羅演算法 主元素問題
  2. #include "stdafx.h"
  3. #include "RandomNumber.h"
  4. #include <cmath>
  5. #include <iostream>
  6. usingnamespace std;  
  7. //判定主元素的蒙特卡羅演算法
  8. template<class Type>  
  9. bool Majority(Type *T,int n)  
  10. {  
  11.     RandomNumber rnd;  
  12. int i = rnd.Random(n);  
  13.     Type x = T[i];  //隨機選擇陣列元素
  14. int k = 0;  
  15. for(int j=0; j<n; j++)  
  16.     {  
  17. if(T[j] == x)  
  18.         {  
  19.             k++;  
  20.         }  
  21.     }  
  22. return (k>n/2);  //k>n/2時,T含有主元素
  23. }  
  24. //重複k次呼叫演算法Majority
  25. template<class Type>  
  26. bool MajorityMC(Type *T,int n,double e)  
  27. {  
  28. int k = ceil(log(1/e)/log((float)2));  
  29. for(int i=1; i<=k; i++)  
  30.     {  
  31. if(Majority(T,n))  
  32.         {  
  33. returntrue;  
  34.         }  
  35.     }  
  36. returnfalse;  
  37. }  
  38. int main()  
  39. {  
  40. int n = 10;  
  41. float e = 0.001;  
  42. int a[] = {5,5,5,5,5,5,1,3,4,6};  
  43.     cout<<"陣列a的元素如下:"<<endl;  
  44. for(int i=0; i<10; i++)  
  45.     {  
  46.         cout<<a[i]<<" ";  
  47.     }  
  48.     cout<<endl;  
  49.     cout<<"呼叫MajorityMC判斷陣列是否含有主元素結果是:"<<MajorityMC(a,n,e)<<endl;  
  50. }  
//隨機化演算法 蒙特卡羅演算法 主元素問題
#include "stdafx.h"
#include "RandomNumber.h"
#include <cmath>
#include <iostream>
using namespace std;

//判定主元素的蒙特卡羅演算法
template<class Type>
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含有主元素
}

//重複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;
}

int main()
{
	int n = 10;
	float e = 0.001;
	int a[] = {5,5,5,5,5,5,1,3,4,6};
	cout<<"陣列a的元素如下:"<<endl;
	for(int i=0; i<10; i++)
	{
		cout<<a[i]<<" ";
	}
	cout<<endl;
	cout<<"呼叫MajorityMC判斷陣列是否含有主元素結果是:"<<MajorityMC(a,n,e)<<endl;
}
    程式執行結果如圖:


     3、素數測試問題

數學原理

     Wilson定理:對於給定的正整數n,判定n是一個素數的充要條件是(n-1)! -1(mod n)。
     費爾馬小定理:如果p是一個素數,且0<a<p,則a^(p-1)(mod p)。 
     二次探測定理:如果p是一個素數,且0<x<p,則方程x^21(mod p)的解為x=1,p-1。

     Carmichael數:費爾馬小定理是素數判定的一個必要條件。滿足費爾馬小定理條件的整數n未必全是素數。有些合數也滿足費爾馬小定理的條件,這些合數稱為Carmichael數。前3個Carmichael數是561,1105,1729。Carmichael數是非常少的,在1~100000000的整數中,只有255個Carmichael數。

求a^m(mod n)的演算法

     設m的二進位制表示為bkbk-1…b1b0(bk=1)。
     例:m=41=101001(2),b
kbk-1…b1b0=101001,(k=5)。
     可以這樣來求a^m:初始C←1。
     b
5=1:C←C^2(=1),∵bk=1,做C←a*C(=a);
     b5b4=10:C←C^2(=a^2),∵bk-1=0,不做動作;
     b5b4b3=101:C←C^2(=a^4),∵bk-2=1,做C←a*C(=a^5);
     b5b4b3b2=1010:C←C^2(=a^10),∵bk-3= b2=0,不做動作;
     b5b4b3b2b1=10100:C←C^2(=a^20),∵bk-4= b1=0,不做動作;
     b5b4b3b2b1b0=101001:C←C^2(=a^40),∵bk-5= b0=1,做C←a*C(=a^41)。
     最終要對am求模,而求模可以引入到計算中的每一步:
     即在求得C2及a*C之後緊接著就對這兩個值求模,然後再存入C。
     這樣做的好處是儲存在C中的最大值不超過n-1,
     於是計算的最大值不超過max{(n-1)^2,a(n-1)}。
     因此,即便am很大,求am(mod n)時也不會佔用很多空間。

     程式具體程式碼如下:

[cpp] view plaincopyprint?
  1. //隨機化演算法 蒙特卡羅演算法 素數測試問題
  2. #include "stdafx.h"
  3. #include "RandomNumber.h"
  4. #include <cmath>
  5. #include <iostream>
  6. usingnamespace std;  
  7. //計算a^p mod n,並實施對n的二次探測
  8. void power(unsigned int a,unsigned int p,unsigned int n,unsigned int &result,bool &composite)  
  9. {  
  10.     unsigned int x;  
  11. if(p == 0)  
  12.     {  
  13.         result = 1;  
  14.     }  
  15. else
  16.     {  
  17.         power(a,p/2,n,x,composite);     //遞迴計算
  18.         result = (x*x)%n;               //二次探測
  19. if((result == 1) && (x!=1) && (x!=n-1))  
  20.         {  
  21.             composite  = true;  
  22.         }  
  23. if((p%2)==1)  
  24.         {  
  25.             result = (result*a)%n;  
  26.         }  
  27.     }  
  28. }  
  29. //重複呼叫k次Prime演算法的蒙特卡羅演算法
  30. bool PrimeMC(unsigned int n,unsigned int k)  
  31. {  
  32.     RandomNumber rnd;  
  33.     unsigned int a,result;  
  34. bool composite = false;  
  35. for(int i=1; i<=k; i++)  
  36.     {  
  37.         a = rnd.Random(n-3)+2;  
  38.         power(a,n-1,n,result,composite);  
  39. if(composite || (result!=1))  
  40.         {  
  41. returnfalse;  
  42.         }  
  43.     }  
  44. returntrue;  
  45. }  
  46. int main()  
  47. {  
  48. int k = 10;  
  49. for(int i=1010;i<1025;i++)  
  50.     {  
  51.         cout<<i<<"的素數測試結果為:"<<PrimeMC(i,k)<<endl;  
  52.     }  
  53. return 0;  
  54. }  
//隨機化演算法 蒙特卡羅演算法 素數測試問題
#include "stdafx.h"
#include "RandomNumber.h"
#include <cmath>
#include <iostream>
using namespace std;

//計算a^p mod n,並實施對n的二次探測
void power(unsigned int a,unsigned int p,unsigned int n,unsigned int &result,bool &composite)
{
	unsigned int x;
	if(p == 0)
	{
		result = 1;
	}
	else
	{
		power(a,p/2,n,x,composite);		//遞迴計算
		result = (x*x)%n;				//二次探測

		if((result == 1) && (x!=1) && (x!=n-1))
		{
			composite  = true;
		}

		if((p%2)==1)
		{
			result = (result*a)%n;
		}
	}
}

//重複呼叫k次Prime演算法的蒙特卡羅演算法
bool PrimeMC(unsigned int n,unsigned int k)
{
	RandomNumber rnd;
	unsigned int a,result;
	bool composite = false;

	for(int i=1; i<=k; i++)
	{
		a = rnd.Random(n-3)+2;
		power(a,n-1,n,result,composite);
		if(composite || (result!=1))
		{
			return false;
		}
	}
	return true;
}

int main()
{
	int k = 10;
	for(int i=1010;i<1025;i++)
	{
		cout<<i<<"的素數測試結果為:"<<PrimeMC(i,k)<<endl;
	}
	return 0;
}
     程式執行結果如圖: