1. 程式人生 > >普林斯頓大學演算法第一部分學習總結(Week1-Percolation)

普林斯頓大學演算法第一部分學習總結(Week1-Percolation)

Algorithms Part1課程第一週的Programming Assignment是Percolation問題,該問題是Union-Find演算法的一個應用例項。

模型描述:

Percolation即滲透過程,其模型如下:一個方形“水槽”(system)由N*N個方格(site)組成,每個方格有開啟(open)或關閉(blocked)兩個狀態,相鄰(即上下左右)的開啟格子能夠構成一條同路。如果一個格子處於開啟狀態,並且與頂端的一行能夠通過開啟的格子實現連通(connected)通路,我們說這個格子處於充滿水(full)的狀態;如果一個“水槽”(system)的頂端格子與底部格子通過開啟的格子實現了連通,我們稱這個“水槽”是可以滲透(percolates)的。如下圖所示:

問題描述:

本次作業要求實現上述模型,並計算出“水槽”達到滲透狀態時開啟格子的數量佔所有格子總數的比例(p)。通過蒙特卡洛模擬方法,採用多次模擬取樣進行p值的估算。對於不同大小的“水槽”有如下模擬曲線,左圖為20*20大小,右圖為100*100大小:

Percolation程式設計分析:

按照作業要求,需要按照以下API建立一個Percolation類作為資料抽象型別:

  • Percolation(int N):該構造方法對模型初始化,我們建立一個N*N大小的一維布林陣列作為“水槽”,陣列值記錄每個格子的開閉狀態,初始狀態為關閉。此外,我們需要另一個(N*N+2)大小的一維陣列儲存連通關係,根據Union-Find演算法的學習,我們建立型別為WeightedQuickUnionUF的陣列。大小之所以為(N*N+2),是因為在檢查是否滲透的時候,可以通過在頂部與底部設定兩個虛擬點
    ,其與頂端或底部的一排是相連通的,這樣檢查是否滲透的時候,無需遍歷頂部或底部一排的每一個點,只需要檢查兩個虛擬點是否連通即可,如下圖所示:

  • open(int i, int j):檢查座標為(i, j)的點是否開啟,如果沒有開啟,則將其開啟。注意i,j的取值範圍為1~N。此外,開啟格子後,需要檢查其上下左右的格子是否同樣已經開啟,如果已經開啟,則通過WeightedQuickUnionUF的union()方法將兩個格子連通起來;另外根據作業要求,需要首先檢查是否越界,丟擲java.lang.IndexOutOfBoundsException()錯誤;
  • isOpen(int i, int j):只需要返回該點的開閉狀態值;
  • isFull(int i, int j):檢查該點是否已經開啟並且已經與頂部的虛擬點(相當於頂部的一排)連通,用WeightedQuickUnionUF的connected()方法檢測;
  • percolates():檢查頂部的虛擬點與底部的虛擬點是否連通,用WeightedQuickUnionUF的connected()方法檢測;

注意的一點是,在用一維陣列模擬二維“水槽”時,我們用i,j座標進行表示,(i, j)表示的是陣列中序號為(i - 1)*N + (j - 1)的元素;此外,由於虛擬點的設定,座標的對應容易出現混亂,需要首先考慮清楚這個問題。

更重要的一個問題:BackWash即迴流/倒灌問題,按照上面的方法設定虛擬點雖然使得檢查是否percolate變得方便,但如果水已經滲透到底部,由於虛擬點與底部一排都是連通的,則會通過底部開啟的格子向上“灌水”,如下圖所示:

解決的方法是:在構造方法中額外定義一個(N*N+1)大小的陣列,其不包括底部的虛擬點,其他與(N*N+2)大小的陣列一致,在isFull()方法中,我們使用這個(N*N+1)的陣列進行檢查,也就是說,如果底部的格子確實沒有與頂部的虛擬點連通,是不會“灌滿”的。下面用圖來說明這一過程:

             

左圖中由於底部有虛擬點,右下角的格子出現了倒灌;而右圖中由於底部沒有虛擬點,不會出現倒灌。注意僅僅在執行percolates()方法時有這一區別。

PercolationStats程式設計分析:

用蒙特卡洛(Monte Carlo)方法進行模擬分析,確定滲透閾值p,具體方法是:

  • 初始化將所有N*N個格子置於關閉狀態;
  • 每次隨機選擇一個格子進行開啟操作,直到系統達到滲透狀態;
  • 統計此時開啟的格子數,與全部格子數相比計算一個p值的樣本;
  • 獨立重複T次上述實驗,最後計算出T個p值的樣本,記為x1,x2……xT。

進行以下統計操作。

按照下式計算均值與方差:

假設T值足夠大(至少30次),則可以根據下式計算置信區間在95%的的滲透閾值範圍:

所有程式碼見本機。

總結:

如何根據API編譯JAVA程式:

1.把API所需要做的內容全都用文字詳細寫出來,每個步驟該做什麼;

2.可以寫出虛擬碼,類似於演算法課上所要求的,詳盡寫出;

3.選好資料結構,簡單變成不需要;

4.Coding

經驗教訓:

0.引數的加入,在Run--Run Configuration--Java application--Arguments--Program Arguments裡面加入text的含text的檔名,該檔案要存放在專案的子目錄下(進入專案的目錄放在和bin,src在一起);

1.有bound限制條件的,先把bound寫好,再寫其他的條件(if(y-1>=0 && openState[x*N+(y-1)] );

2.新建的物件一定要在建構函式裡面初始化,且不能在建構函式裡面再寫一次類名,否則會認為是一個新的物件;

grid = new WeightedQuickUnionUF(N*N+2);

WeightedQuickUnionUF grid;

3.若遇到“重複直到”,“repeat until”這樣的語句,要用到while進行判定;

while(!perco.percolates())

4.double/int = int, 出現出現很多隱藏錯誤,比如如下結果如果不轉化就是0. 需要重新令一個變數double x = int,然後再用除法相除。

doubletransferNsqure=N*N;

int opened++;

x[i]=opened/transferNsqure;