樹狀圖與樹狀目錄


阿瑟 發表



先建立有兩個欄位的資料表: 一欄位紀錄節點名稱, 另一欄位紀錄父節點名稱. 然後利用遞迴函式即可建立樹狀圖.

其實要製作一個樹狀圖並不困難.

只要記得在寫任何程式千萬不要一頭栽入, 而要仔細思考程式之中的邏輯. 就程式撰寫的部份, 最起碼要經過兩個階段: 邏輯設計 (Logical Design)實體設計 (Actual Design). (Microsoft Solution Framework, MSF)

邏輯設計著重於程式邏輯與演算法的籌劃, 用"整體"的概念去思考程式中的每個功能. 就拿樹狀圖來說, 你可以拿一張紙畫出一樹狀圖. 然後試想:

節點名稱
父節點
亞洲
none
印度
亞洲
台灣
亞洲
中國大陸
亞洲
菲律賓
亞洲
北美洲
none
美利堅合眾國
北美洲
墨西哥
北美洲
加拿大
北美洲
如果以上是您所建立的資料表, 而 none 代表沒有父節點, 即該節點為最上層項目. 那要如何建立樹狀圖呢?

如果你將樹狀圖在紙上畫出來:
  • 亞洲
  • 印度
  • 台灣
  • 中國大陸
  • 菲律賓
  • 北美洲
  • 美利堅合眾國
  • 墨西哥
  • 加拿大

  • 試想, 如果你先在資料表中找出所有最上層節點, 然後再針對每個節點去找其下層節點, 就可以建立樹狀圖了. 到這裡, 我們完成了邏輯設計部分.

    實體設計的部份, 我們要開始思考如何撰寫在邏輯設計部分寫出的程式邏輯, 或演算法.

    如果我們已經建立了資料庫連結 (Database Connection), 我們可以用:

    set rsm = Server.CreateObject("ADODB.Recordset")
    sql="SELECT *FROM 資料表 WHERE 父節點='" & none & "' ORDER BY 節點名稱"
    rsm.Open sql, cons,1, 3
    

    來取得所有最上層的節點.

    之後用迴圈來輸出所有的最上層節點:
    set rsm = Server.CreateObject("ADODB.Recordset")
    sql="SELECT *FROM 資料表 WHERE 父節點='" & none & "' ORDER BY 節點名稱"
    rsm.Open sql, cons,1, 3
    
    if NOT rsm.eof
      while NOT rsm.eof
        response.write rsm("節點名稱")
        rsm.movenext
      wend
    end if
    

    這樣就取得了最上層節點了.

    至於下一層節點, 我們可以用巢狀迴圈 (Nested Looping), 也就是將另外一個迴圈至於原本的迴圈中, 來針對每一個上層結果去尋找其下層結果:
    set rsm = Server.CreateObject("ADODB.Recordset")
    sql="SELECT *FROM 資料表 WHERE 父節點='" & none & "' ORDER BY 節點名稱"
    rsm.Open sql, cons,1, 3
    
    if NOT rsm.eof
      while NOT rsm.eof
        response.write rsm("節點名稱") & "<br>"
    
        set rsm2 = Server.CreateObject("ADODB.Recordset")
        sql2="SELECT *FROM 資料表 WHERE 父節點='" & rsm("節點名稱") & "' ORDER BY 節點名稱"
        rsm2.Open sql2, cons,1, 3
    
        if NOT rsm2.eof
          while NOT rsm2.eof
            response.write rsm2("節點名稱") & "<br>"    
            rsm2.movenext
          wend
        end if
        rsm.movenext
      wend
    end if
    

    利用巢狀迴圈, 這樣就可以找到所有上層節點的子節點.

    但是如果今天您要做的樹狀圖是:

  • 中國大陸
  • 江蘇省
  • 上海市
  • 河北省
  • 北京市
  • 天安門廣場
  • 美國
  • 紐約
  • 紐約市
  • 自由女神像

  • 如果是這種層數不同, 剛剛的演算法就不敷使用, 因為巢狀迴圈每增加一個新層次就必須在最底端迴圈再增加一新迴圈.

    由於巢狀迴圈在數量掌控上比較困難, 我們必須思考其他的演算法來解決問題.

    其實幾乎多數用迴圈能解決的問題, 用遞迴函式 (Recursive Function) 幾乎都有辦法達到同樣的效果.

    如果您不清楚什麼是遞迴函式, 您可以看看這個範例:

    function calc(number)
     if number>1 then
      calc = number + calc(number-1)
     else
      calc = 1
     end if
    end function
    
    如果你執行 calc(10), 第一次執行由於參數大於1, 函式回傳的是10 + calc(9)
    而calc(9)也是同樣的情況, 回傳 9 + calc(8)
    依次類推...到最後calc(2)會回傳 2 + calc(1)
    calc(1)會直接回傳1
    這時候函式已經完成了遞迴特性, 會將calc(1)的結果回傳到 calc(2)
    calc(2)回傳結果就變成 2 + 1 = 3, calc(2)的結果就會回傳到 calc(3)
    calc(3)回傳結果就變成 3 + 3 = 6, 依次類推.
    實際上calc(10)執行結果就是: 10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 , 就是數學中的 10! (10階層)囉.

    我們可以利用遞迴函式的特性來重新撰寫樹狀圖程式: 當節點下還有節點的時候, 就繼續搜尋更下層節點.

    我們可以先寫一個會搜尋指定父節點的函式, 然後在每個找到的節點下再執行同樣的函式來尋找其子節點, 善用遞迴的特性.

    以下是範例遞迴函式:

    <%
    function extendTree(parent)
      set rsm = Server.CreateObject("ADODB.Recordset")
      sql="SELECT *FROM 資料表 WHERE 父節點='" & parent & "' ORDER BY 節點名稱"
      rsm.Open sql, cons,1, 3
    
      if NOT rsm.eof Then
        response.write "<dir>"
    
        while NOT rsm.eof
            response.write "<li>" & rsm("節點名稱")
    
          extendTree rsm("節點名稱")
          response.write "</li>"
          rsm.movenext
        wend
    
        response.write "</dir>"
      end if
    
      rsm.close
    end function
    %>
    


    要製作樹狀圖, 只要執行 extendTree("none") 來找出所有父節點為 none, 也就是沒有父節點的資料即可.
    執行 extendTree("none") 的時候, 函式會找出所有最上層節點, 如 "中國大陸" 與 "美國".
    之後函式會以每個節點為參數再次執行函式, 如找到"中國大陸"就會執行 extendTree("中國大陸")
    如果沒有找到任何子結點當然extendTree()就會自動停止 (就跟之前範例到1會自動停止遞迴是一樣的), 因此利用遞迴這種演算法可以很彈性的設計自己的樹狀圖, 不管怎麼新增節點都不需要修改程式.



    這個範例遞迴函式唯一的問題就是資料表中的節點不能夠有重複的名稱, 如上海市下有"市政府"子節點的話, 紐約市就不能有同樣名稱的子節點, 否則會混淆, 產生怪異的輸出結果.

    大家可以再研究看看有沒有別的寫法, 很有趣的.

       

    最後更新日期: 4/10/2004 8:21:25 PM