三、實現 1、毗鄰目錄模式(adjacency list model) 這種模式我們經常用到,很多的教程和書中也介紹過。我們通過給每個節點增加一個屬性 parent 來表示這個節點的父節點從而將整個樹狀結構通過平面的表描述出來。根據這個原則,例子中的數據可以轉化成如下的表: 以下是代碼: 復制代碼 代碼如下: +-----------------------+ | parent | name | +-----------------------+ | | Food | | Food | Fruit | | Fruit | Green | | Green | Pear | | Fruit | Red | | Red | Cherry | | Fruit | Yellow | | Yellow | Banana | | Food | Meat | | Meat | Beef | | Meat | Pork | +-----------------------+
我們看到 Pear 是Green的一個子節點,Green是Fruit的一個子節點。而根節點'Food'沒有父節點。 為了簡單地描述這個問題,這個例子中只用了name來表示一個記錄。 在實際的數據庫中,你需要用數字的id來標示每個節點,數據庫的表結構大概應該像這樣:id, parent_id, name, descrīption。 有了這樣的表我們就可以通過數據庫保存整個多級樹狀結構了。 顯示多級樹,如果我們需要顯示這樣的一個多級結構需要一個遞歸函數。 以下是代碼: 復制代碼 代碼如下: ?php // $parent is the parent of the children we want to see // $level is increased when we go deeper into the tree, // used to display a nice indented tree function display_children($parent, $level) { // 獲得一個 父節點 $parent 的所有子節點 $result = mysql_query(" SELECT name FROM tree WHERE parent = '" . $parent . "' ;" ); // 顯示每個子節點 while ($row = mysql_fetch_array($result)) { // 縮進顯示節點名稱 echo str_repeat(' ', $level) . $row['name'] . "/n"; //再次調用這個函數顯示子節點的子節點 display_children($row['name'], $level+1); } } ?
對整個結構的根節點(Food)使用這個函數就可以打印出整個多級樹結構,由于Food是根節點它的父節點是空的,所以這樣調用: display_children('',0)。將顯示整個樹的內容: 復制代碼 代碼如下: Food Fruit Red Cherry Yellow Banana Meat Beef Pork
如果你只想顯示整個結構中的一部分,比如說水果部分,就可以這樣調用:display_children('Fruit',0); 幾乎使用同樣的方法我們可以知道從根節點到任意節點的路徑。比如 Cherry 的路徑是 "Food Fruit Red"。 為了得到這樣的一個路徑我們需要從最深的一級"Cherry"開始, 查詢得到它的父節點"Red"把它添加到路徑中,然后我們再查詢Red的父節點并把它也添加到路徑中,以此類推直到最高層的"Food",以下是代碼: 復制代碼 代碼如下: ?php // $node 是那個最深的節點 function get_path($node) { // 查詢這個節點的父節點 $result = mysql_query(" SELECT parent FROM tree WHERE name = '" . $node ."' ;" ); $row = mysql_fetch_array($result); // 用一個數組保存路徑 $path = array(); // 如果不是根節點則繼續向上查詢 // (根節點沒有父節點) if ($row['parent'] != '') { // the last part of the path to $node, is the name // of the parent of $node $path[] = $row['parent']; // we should add the path to the parent of this node // to the path $path = array_merge(get_path($row['parent']), $path); } // return the path return $path; } ?
如果對"Cherry"使用這個函數:print_r(get_path('Cherry')),就會得到這樣的一個數組了: 復制代碼 代碼如下: Array ( [0] = Food [1] = Fruit [2] = Red )
看到了吧,只要一個查詢就可以得到所有這些節點。為了能夠像上面的遞歸函數那樣顯示整個樹狀結構,我們還需要對這樣的查詢進行排序。用節點的左值進行排序: 復制代碼 代碼如下: SELECT * FROM tree WHERE lft BETWEEN 2 AND 11 ORDER BY lft ASC;
剩下的問題如何顯示層級的縮進了。 以下是代碼: 復制代碼 代碼如下: ?php function display_tree($root) { // 得到根節點的左右值 $result = mysql_query(" SELECT lft, rgt FROM tree WHERE name = '" . $root . "' ;" ); $row = mysql_fetch_array($result); // 準備一個空的右值堆棧 $right = array(); // 獲得根基點的所有子孫節點 $result = mysql_query(" SELECT name, lft, rgt FROM tree WHERE lft BETWEEN '" . $row['lft'] . "' AND '" . $row['rgt'] ."' ORDER BY lft ASC ;" ); // 顯示每一行 while ($row = mysql_fetch_array($result)) { // only check stack if there is one if (count($right) 0) { // 檢查我們是否應該將節點移出堆棧 while ($right[count($right) - 1] $row['rgt']) { array_pop($right); } } // 縮進顯示節點的名稱 echo str_repeat(' ',count($right)) . $row['name'] . "/n"; // 將這個節點加入到堆棧中 $right[] = $row['rgt']; } } ?
如果你運行一下以上的函數就會得到和遞歸函數一樣的結果。只是我們的這個新的函數可能會更快一些,因為只有2次數據庫查詢。 要獲知一個節點的路徑就更簡單了,如果我們想知道Cherry 的路徑就利用它的左右值4和5來做一個查詢。 復制代碼 代碼如下: SELECT name FROM tree WHERE lft 4 AND rgt 5 ORDER BY lft ASC;
這樣就會得到以下的結果: 以下是代碼: 復制代碼 代碼如下: +------------+ | name | +------------+ | Food | | Fruit | | Red | +------------+
不相信?自己算一算啦。 用這個簡單的公式,我們可以很快的算出"Fruit 2-11"節點有4個子孫節點,而"Banana 8-9"節點沒有子孫節點,也就是說它不是一個父節點了。 很神奇吧?雖然我已經多次用過這個方法,但是每次這樣做的時候還是感到很神奇。 這的確是個很好的辦法,但是有什么辦法能夠幫我們建立這樣有左右值的數據表呢?這里再介紹一個函數給大家,這個函數可以將name和parent結構的表自動轉換成帶有左右值的數據表。 以下是代碼: 復制代碼 代碼如下: ?php function rebuild_tree($parent, $left) { // the right html' target='_blank'>value of this node is the left value + 1 $right = $left+1; // get all children of this node $result = mysql_query(" SELECT name FROM tree WHERE parent = '" . $parent . "' ;" ); while ($row = mysql_fetch_array($result)) { // recursive execution of this function for each // child of this node // $right is the current right value, which is // incremented by the rebuild_tree function $right = rebuild_tree($row['name'], $right); } // we've got the left value, and now that we've processed // the children of this node we also know the right value mysql_query(" UPDATE tree SET lft = '" . $left . "', rgt= '" . $right . "' WHERE name = '" . $parent . "' ;" ); // return the right value of this node + 1 return $right + 1; } ?