首先,介紹一下樹狀結構在DB中的儲存。
使用二維表,如,儲存樹狀結構:
現在,我們的目標是想要把這一樹狀結構表示成:
由可以看出它們之間含有一種層級關係,查看原始碼,如下:
現在,演算法的思路是,先將樹狀結構按照list的順序排列出來,這個順序其實就是去掉了UL和LI標籤的順序,如:
要實現這個順序其實很簡單。
再來看看我們的樹
abcdef其實就是這顆樹的先序遍曆的結果。
那麼,如果現在我們只有一張二維表,那麼我們要怎樣產生這個先序遍曆的結果呢?(不使用遞迴)
觀察這張二維表:
node到fatherNode是一對一映射,而fatherNode到node之間則是一對多映射。
也就是說我們可能不太容易知道某個節點的兄弟節點(需要先確定父節點,再通過父節點query兄弟節點),但是確定某個節點的子節點是非常容易的。
如,節點a的所有子節點,只要query fatherNode=a即可。
因此,我們可以使用廣搜的思想來解決這個問題。
廣搜就是先遍曆離根最近的第一層節點,將這些節點放入queue中,然後從queue頭不斷取出節點,再遍曆這個節點的所有子節點,並將其子節點放入queue的隊尾,依次下去,知道所有節點都遍曆到為止。
拿我們的樹來說,就是
但是,如果直接用廣搜這個演算法的話,我們得到的序列是:
abdcef
與我們所期待的不同。
所以,改進一下該演算法。
為了儲存我們的結構,建立一個列表。現在,我們就有兩個列表了,一個是廣搜用的queue,另一個是儲存結果的list。
當廣搜每摘掉一個頭元素,並將該元素的子節點放入queue中時,就在儲存的list中的相應位置插入這些子節點們。那麼相應位置是哪裡呢?就是被摘掉的這個頭元素。
例如,現在queue的形態為 “a” ,那麼現在要做的操作是把queue的頭,a元素摘掉,並且將其子節點"b d"加入queue中,與此同時,在list中尋找到a的位置,並將“b d”添加到其後面。此時,queue的形態變為"b d",而list則變為"a b d"。重複此步驟,現在摘掉queue中的頭元素,即b,並且將其子節點(c)放入queue尾部,同時在list的b元素後面添加b的子節點(c),此時,queue的形態變為“d c”,而list的形態變為“a b c d”。依照此方式一直進行到廣搜結束,即進行到queue為空白。
該演算法結束時,就可以得到 a b c d e f 這一我們希望見到的序列了。
但是,這一序列丟失了一項很重要的資訊,就是層次資訊。
所以,再對演算法進行一下改進,我們引入一個結構體
<node, level>
使用 level 屬性來記錄某個節點所處的層次。
那麼level這個屬性在什麼時候進行賦值呢?
在將某個元素的子項目插入list中時。
如何賦值呢?
這個問題很簡單,因為是為某個元素插入子項目,因此,被插入的這些元素的level相當於其父節點的 level + 1
所以,list最後就變成了這樣一個結構:
node list的順序 “abcdef”,以及其level屬性都是必須的。
現在,我們既知道了節點的順序,也知道了節點中各個點的關係,因此就可以構建又ul和li表示的樹狀結構了。
我們再觀察一下原始碼:
發現,
(1)當level增大時,就會出現一個<ul>標籤。
(2)當level減小時,就會出現</ul>標籤,而</ul>標籤的數量與level減小的程度有關,即減小的level是N,就應該出現N個</ul>標籤。
(3)對於每一個list中的item,都由一個<li></li>標籤對包圍。
由上述規則,我們就可以寫出如下程式:
Stack<String> stack = new Stack<String>(); String item = "</ul>"; String res = ""; ArrayList<SightLevelPair> list = this.sightTree.getList(); int lastLevel = 0; int currLevel = 0; if(list.size() != 0){ Iterator<SightLevelPair> iter = list.iterator(); while(iter.hasNext()){ SightLevelPair pair = iter.next(); currLevel = pair.getLevel(); if(currLevel > lastLevel){ res += "<ul>"; stack.push(item); }else if(currLevel < lastLevel){ int delta = lastLevel - currLevel; while(delta > 0){ res += stack.pop(); --delta; } } res += "<li>" + pair.getSight().getSightName() + "</li>"; lastLevel = currLevel; } while(0 != stack.size()){ res += stack.pop(); } }
可以看到程式中有一個stack,它是用來儲存“</ul>”字串的。每當出現一個level增加時,就在最終在字串後加入一個“<ul>”,同時,將一個“</ul>”壓入stack中,並且在遇到level減少時,將相應數量的"</ul>"拋出。
結果還是很理想的:
產生這個樹用了自訂標籤,即定義了一個SimpleTagSupport的子類,用來處理自訂標籤。
製作自訂標籤的方法【在此】。