標籤:技術分享 分享 需要 使用 pos 補充 toc 迴圈 技術
【Java】 使用Java實現一則演算法前情提要
在學習Java的過程中,我的一個基友扔給了我一道演算法題,為了檢驗自己對Java的學習情況我決定使用Java解決這道演算法題。
具體問題
現有一株K叉樹,我們知道其前序走訪與後序遍曆,也知道K的值,求該K叉樹有多少種可能形態。如一13叉樹,前序走訪為abejkcfghid,後序遍曆為jkebfghicda,則其可能形態有207352860種。
問題分析
根據遍曆的定義我們可以知道:
前序走訪的第一個字母和後序遍曆的最後一個字母是他的根。
前序走訪的根的後一個字母是它最左邊的子樹。
後續遍曆的根的前一個字母是它最右邊的子樹。
以題目為例:(這裡我們從後序遍曆入手)
a為該樹的根,d為a的最右子樹,其餘字母都是a的後裔;
以d為根,在前序走訪中d右邊的為d的後裔(無);d左邊的為d的兄弟及其後裔(即bejkcfghi),其中c為除了d外a的最右子樹。
以c為根,在前序走訪中c右邊的為c的後裔(即fghi), c左邊的為c左邊的兄弟及其後裔(即bejk),c的後裔的前序走訪為fghi,後序遍曆為fghi,i為c的最右子樹;c的左邊的兄弟及其後裔的前序走訪為bejk,後序遍曆為jkeb。b為除了d,c外a的最右子樹;
先處理c左邊的兄弟及其後裔,可知b為a的子樹,c、d的兄弟;e為b的子樹;j、k為e的子樹;
處理c的後裔,根據上述處理方法易得f、g、h、i都是c的子樹;
,可見a有3個子樹,b有1個子樹,c有4個子樹,e有2個子樹。因為是13叉樹,當一個根節點有n個子樹,則該結點與子樹的排列可能性為C(n,13),故該13叉樹總的可能形態有C(3,13)* C(1,13)* C(4,13)* C(2,13) = 207352860。
總結以上過程,我們需要的東西是一個能將K叉樹不斷拆分,能求出每一個根節點子樹數量的方法。
程式實現
首先,我們要明白我們需要什麼:
一個main類,裡面能初始化我們所需要的資料,包括題目給出的K,前序走訪和後序遍曆,最好還能最後處理結果並輸出。
在分析過程中我們需要用到大量的C(n,k),如果每個C(n,k)都單獨運算的話非常麻煩,我們可以用一個數組計算並儲存它們。
下面自然是重頭戲了,通過方法實現演算法。這個方法:
1.需要能根據給定的前序走訪和後序遍曆判定出根節點;
2.通過根節點拆分前序走訪和後序遍曆,同時計算每個根節點子樹的數量;
3.能夠調用自己,因為拆分自己是要不斷迴圈的,此外我們還需要一個數組用於記錄有多少個根,每個跟有多少個子樹,這個數組顯然是應該獨立在方法外的。
實際程式
public class qianhou { //主類 public static void main(String[] args){ String qian = "abejkcfghid"; //初始化輸入 String hou = "jkebfghicda"; byte k = 13; long poss = 1; //結果 char [] dlr = qian.toCharArray(); //Java中對String類型利用複雜,轉化成char數組方便操作 char [] lrd = hou.toCharArray(); //dlr前序走訪,lrd後序遍曆 Jisuan a1 = new Jisuan(); int[] zuh=a1.setarray(k); //將方法計算的組合數結果 a1.find(dlr,lrd); for (int i=1; i<a1.ccount+1; i++){ //計算結果 poss = poss * zuh[a1.acco[i]]; } System.out.println(poss); }} class Jisuan{ int[] acco = new int[10000]; //建立數組記錄根節點子樹個數(也可以用ArrayList,雖然給ArrayList數組內的值++比較麻煩,但可以控制數組大小。注意不能寫在方法中 int ccount = 0; //acco數組指標 public int[] setarray(byte k){ //記錄C(n,k)值的數組 int[] zuhe = new int[k+1]; zuhe[0] = 1; for (int i=1; i<=k; i++){ zuhe[i] = 1; zuhe[i] =zuhe[i-1]*(k+1-i)/i; } return zuhe; } public void find(char[] dlr , char[] lrd){ //調用對象應為前序走訪與後序遍曆,因為有完整的資訊 int i1 = 0, i2=0, i3 = 0; //初始化指標 int l = dlr.length; //初始化遍曆長度 while (dlr[i1] != lrd[l-1]){ //尋找前序走訪中的最右子樹位置 i1++; } //此處由於時間關係,未使用try校正初始條件是否合理,應該補充 if (l != 1){ //如長度為1則無子樹,不需要繼續分割 if (i1!=0){ //如果最右子樹在前序走訪中位置不為0,說明無左側兄弟及其後裔,左側無需分割 char[] ldlr = new char[i1]; //將左側兄弟及其後裔的前序走訪與後序遍曆分割分割 char[] llrd = new char[i1]; for (i2=0; i2<i1;i2++){ ldlr[i2] = dlr[i2]; llrd[i2] = lrd[i2]; } acco[ccount] = acco[ccount]+1; //根下子樹數量+1 find(ldlr,llrd); //先分割左側兄弟及其後裔,先遍曆根的所有兄弟,無需大量調控記錄數組的指標 } if (i1!=l-1){ //如果最右子樹在前序走訪中位置不為末尾,說明無後裔,右側無需分割 char[] rdlr = new char[l-i1-1];//將其後裔的前序走訪與後序遍曆分割分割 char[] rlrd = new char[l-i1-1]; for (i3=0; i3<l-i1-1;i3++){ rdlr[i3] = dlr[i3+1+i1]; rlrd[i3] = lrd[i3+i1]; } ccount++; //數組指標指向下一個根 acco[ccount] = 1; //根下子樹數量+1 find(rdlr,rlrd); } } } }
總結
Java雖然並不適合用來寫演算法,但是就是因為這重重障礙和限制條件,寫起演算法來能遇到更多的問題,在解決這些問題的過程中,反而能更加深入的理解Java的內在邏輯和自身的缺陷。
- 這大概就是所謂的時間是檢驗真理的唯一標準了吧...
使用Java實現一則演算法