修改预订树遍历
现在,我们来看看存储树的另一种方法。递归可能很慢,所以我们宁可不使用递归函数。我们也希望最小化数据库查询的数量。我们最好只对每个活动进行一次查询。
我们将从水平的方式展开我们的树。从根节点('Food')开始,并在其左边写1。按照树进行“水果”,并在其旁边写一个2。这样,在每个节点的左侧和右侧写一个数字时,沿着树的边缘走(遍历)。最后一个数字写在“食物”节点的右侧。在这个图像中,您可以看到整个编号的树,还有几个箭头来指示编号顺序。
我们会将这些数字左右调用(例如,“食物”的左侧值为1,正确的值为18)。您可以看到,这些数字表示每个节点之间的关系。因为“红”有数字3和6,它是1-18“食物”节点的后裔。以同样的方式,我们可以说所有左值大于2,右值小于11的节点都是2-11“Fruit”的后代。树结构现在存储在左右值中。这种围绕树和计数节点走动的方法被称为“修改的预订树遍历”算法。
在我们继续之前,让我们看看这些值在我们的表中如何:
请注意,“left”和“right”这个词在SQL中有特殊的含义。因此,我们必须使用'lft'和'rgt'来标识列。还要注意,我们不再需要“父”列了。我们现在有lft和rgt值来存储树结构。
检索树
如果要使用具有左右值的表显示树,则首先必须标识要检索的节点。例如,如果您想要“水果”子树,则必须仅选择左侧值在2和11之间的节点。在SQL中,这将是:
SELECT * FROM tree WHERE lft BETWEEN 2 AND 11;
那回报:
那就是:一个查询中的一棵树。要像我们的递归函数那样显示这个树,我们必须向此查询添加一个ORDER BY子句。如果您从表中添加和删除行,则表可能不会处于正确的顺序。因此,我们应该按照左边的值排列。
SELECT * FROM tree WHERE lft BETWEEN 2 AND 11 ORDER BY lft ASC;
剩下的唯一问题就是缩进。
为了显示树状结构,儿童的缩写比父母稍微缩小一点。我们可以通过保持一堆正确的值来做到这一点。每次从一个节点的子节点开始,您将该节点的正确值添加到堆栈中。您知道该节点的所有子节点都具有小于父节点正确值的正确值,因此通过将当前节点的正确值与堆栈中的最右边节点进行比较,可以看到是否仍然展示该家长的孩子。完成显示节点后,从堆栈中删除其正确的值。如果您计算堆栈中的元素,您将获得当前节点的级别。
<?php
function display_tree($root) {
// retrieve the left and right value of the $root node
$result = mysql_query('SELECT lft, rgt FROM tree '.
'WHERE title="'.$root.'";');
$row = mysql_fetch_array($result);
// start with an empty $right stack
$right = array();
// now, retrieve all descendants of the $root node
$result = mysql_query('SELECT title, lft, rgt FROM tree '.
'WHERE lft BETWEEN '.$row['lft'].' AND '.
$row['rgt'].' ORDER BY lft ASC;');
// display each row
while ($row = mysql_fetch_array($result)) {
// only check stack if there is one
if (count($right)>0) {
// check if we should remove a node from the stack
while ($right[count($right)-1]<$row['rgt']) {
array_pop($right);
}
}
// display indented node title
echo str_repeat(' ',count($right)).$row['title']."n";
// add this node to the stack
$right[] = $row['rgt'];
}
}
?>
如果你运行这个代码,你将得到与上面讨论的递归函数完全相同的树。我们的新功能可能会更快:它不是递归的,它只使用两个查询。
节点的路径
使用这种新算法,我们还必须找到一种新方式来获取特定节点的路径。要获得这个路径,我们需要一个列表,该节点的所有祖先。
用我们新的表格结构,这真的不是很大的工作。例如,当您查看4-5个“樱桃”节点时,您会看到所有祖先的左值小于4,而所有正确的值都大于5.要获取所有祖先,我们可以使用这个查询:
SELECT title FROM tree WHERE lft < 4 AND rgt > 5 ORDER BY lft ASC;
请注意,就像我们之前的查询一样,我们必须使用ORDER BY子句对节点进行排序。此查询将返回:
+-------+
| title |
+-------+
| Food |
| Fruit |
| Red |
+-------+
我们现在只需要加入行以获得“樱桃”的路径。
多少后裔
如果你给我一个节点的左右值,我可以通过使用一点数学来告诉你有多少个后代。
当每个后代用2增加节点的正确值时,可以通过以下方式计算后代数:
descendants = (right – left - 1) / 2
有了这个简单的公式,我可以告诉你,2-11“果实”节点有4个后代节点,8-9个“香蕉”节点只是一个小孩,而不是一个父母。