`
sailorlee
  • 浏览: 42753 次
  • 性别: Icon_minigender_1
  • 来自: 河北唐山
最近访客 更多访客>>
社区版块
存档分类
最新评论

改进前序遍历树—–关于无限分类的问题

    博客分类:
  • PHP
阅读更多

 

现在,让我们看另一种存储树的方法。递归可能会很慢,所以我们就尽量不使用递归函数。我们也想尽量减少数据库查询的次数。最好是每次只需要查询一次。

我们先把树按照水平方式摆开。从根节点开始(“Food”),然后他的左边写上1。然后按照树的顺序(从上到下)给“Fruit”的左边写上2。这 样,你沿着树的边界走啊走(这就是“遍历”),然后同时在每个节点的左边和右边写上数字。最后,我们回到了根节点“Food”在右边写上18。下面是标上 了数字的树,同时把遍历的顺序用箭头标出来了。

我们称这些数字为左值和右值(如,“Food”的左值是1,右值是18)。正如你所见,这些数字按时了每个节点之间的关系。因为“Red”有3和6 两个值,所以,它是有拥有1-18值的“Food”节点的后续。同样的,我们可以推断所有左值大于2并且右值小于11的节点,都是有2-11的 “Food”节点的后续。这样,树的结构就通过左值和右值储存下来了。这种数遍整棵树算节点的方法叫做“改进前序遍历树”算法。

在继续前,我们先看看我们的表格里的这些值:

注意单词“left”和“right”在SQL中有特殊的含义。因此,我们只能用“lft”和“rgt”来表示这两个列。(译注——其实Mysql 中可以用“`”来表示,如“`left`”,MSSQL中可以用“[]”括出,如“[left]”,这样就不会和关键词冲突了。)同样注意这里我们已经不 需要“parent”列了。我们只需要使用lft和rgt就可以存储树的结构。

获取树

如果你要通过左值和右值来显示这个树的话,你要首先标识出你要获取的那些节点。例如,如果你想获得“Fruit”子树,你要选择那些左值在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) {
    // 获得$root节点的左边和右边的值
    $result = mysql_query('SELECT lft, rgt FROM tree '.
                           'WHERE title="'.$root.'";');
    $row = mysql_fetch_array($result); 

    // 以一个空的$right栈开始
    $right = array(); 

    // 现在,获得$root节点的所有后序
    $result = mysql_query('SELECT title, lft, rgt FROM tree '. 

      'WHERE lft BETWEEN '.$row['lft'].' AND '.
                           $row['rgt'].' ORDER BY lft ASC;'); 

    // 显示每一行
  while ($row = mysql_fetch_array($result)) {
    // 检查栈里面有没有元素
    if (count($right)>0) {
      // 检查我们是否需要从栈中删除一个节点
      while ($right[count($right)-1]<$row['rgt']) {
        array_pop($right);
      }
    } 

    // 显示缩进的节点标题
    echo str_repeat('  ',count($right)).$row['title']."\n"; 

    // 把这个节点添加到栈中
    $right[] = $row['rgt'];
  }
  }
  ?>

如果运行这段代码,你可以获得和上一部分讨论的递归函数一样的结果。而这个函数可能会更快一点:他不采用递归而且只是用了两个查询

节点的路径

有了新的算法,我们还要另找一种新的方法来获得指定节点的路径。这样,我们就需要这个节点的祖先的一个列表。

由于新的表结构,这不需要花太多功夫。你可以看一下,例如,4-5的“Cherry”节点,你会发现祖先的左值都小于4,同时右值都大于5。这样,我们就可以使用下面这个查询:

SELECT title FROM tree WHERE lft < 4 AND rgt > 5 ORDER BY lft ASC;

注意,就像前面的查询一样,我们必须使用一个ORDER BY子句来对节点排序。这个查询将返回:

+-------+
| title |
+-------+
| Food  |
| Fruit |
| Red   |
+-------+

我们现在只要把各行连起来,就可以得到“Cherry”的路径了。

有多少个后续节点?How Many Descendants

如果你给我一个节点的左值和右值,我就可以告诉你他有多少个后续节点,只要利用一点点数学知识。

因为每个后续节点依次会对这个节点的右值增加2,所以后续节点的数量可以这样计算:

descendants = (right – left - 1) / 2

利用这个简单的公式,我可以立刻告诉你2-11的“Fruit”节点有4个后续节点,8-9的“Banana”节点只是1个子节点,而不是父节点。

自动化树遍历

现在你对这个表做一些事情,我们应该学习如何自动的建立表了。这是一个不错的练习,首先用一个小的树,我们也需要一个脚本来帮我们完成对节点的计数。

让我们先写一个脚本用来把一个邻接列表转换成前序遍历树表格。

<?php
function rebuild_tree($parent, $left) {
    // 这个节点的右值是左值加1
    $right = $left+1; 

    // 获得这个节点的所有子节点
    $result = mysql_query('SELECT title FROM tree '.
                           'WHERE parent="'.$parent.'";');
    while ($row = mysql_fetch_array($result)) {
        // 对当前节点的每个子节点递归执行这个函数
        // $right 是当前的右值,它会被rebuild_tree函数增加
        $right = rebuild_tree($row['title'], $right);
    } 

    // 我们得到了左值,同时现在我们已经处理这个节点我们知道右值的子节点
    mysql_query('UPDATE tree SET lft='.$left.', rgt='.
                 $right.' WHERE title="'.$parent.'";'); 

    // 返回该节点的右值+1
    return $right+1;
}
?>

这是一个递归函数。你要从rebuild_tree('Food',1); 开始,这个函数就会获取所有的“Food”节点的子节点。

如果没有子节点,他就直接设置它的左值和右值。左值已经给出了,1,右值则是左值加1。如果有子节点,函数重复并且返回最后一个右值。这个右值用来作为“Food”的右值。

递归让这个函数有点复杂难于理解。然而,这个函数确实得到了同样的结果。他沿着树走,添加每一个他看见的节点。你运行了这个函数之后,你会发现左值和右值和预期的是一样的(一个快速检验的方法:根节点的右值应该是节点数量的两倍)。

添加一个节点

我们如何给这棵树添加一个节点?有两种方式:在表中保留“parent”列并且重新运行rebuild_tree()
函数——一个很简单但却不是很优雅的函数;或者你可以更新所有新节点右边的节点的左值和右值。

第一个想法比较简单。你使用邻接列表方法来更新,同时使用改进前序遍历树来查询。如果你想添加一个新的节点,你只需要把节点插入表格,并且设置好parent列。然后,你只需要重新运行rebuild_tree() 函数。这做起来很简单,但是对大的树效率不高。

第二种添加和删除节点的方法是更新新节点右边的所有节点。让我们看一下例子。我们要添加一种新的水果——“Strawberry”,作为“Red” 的最后一个子节点。首先,我们要腾出一个空间。“Red”的右值要从6变成8,7-10的“Yellow”节点要变成9-12,如此类推。更新“Red” 节点意味着我们要把所有左值和右值大于5的节点加上2。

我们用一下查询:

UPDATE tree SET rgt=rgt+2 WHERE rgt>5;
UPDATE tree SET lft=lft+2 WHERE lft>5;

现在我们可以添加一个新的节点“Strawberry”来填补这个新的空间。这个节点左值为6右值为7。

INSERT INTO tree SET lft=6, rgt=7, title='Strawberry';

如果我们运行display_tree() 函数,我们将发现我们新的“Strawberry”节点已经成功地插入了树中:

Food
  Fruit
    Red
      Cherry
      Strawberry
    Yellow
      Banana
  Meat
    Beef
    Pork

缺点

首先,改进前序遍历树算法看上去很难理解。它当然没有邻接列表方法简单。然而,一旦你习惯了左值和右值这两个属性,他就会变得清晰起来,你可以用这 个技术来完成临街列表能完成的所有事情,同时改进前序遍历树算法更快。当然,更新树需要很多查询,要慢一点,但是取得节点却可以只用一个查询。

分享到:
评论
1 楼 fangguojun 2010-08-16  
在插入新记录是提到的把所有左值和右值大于5的节点加上2,这里的5是如何得来的,是父节点的右值减1吗?

相关推荐

    前序遍历树—–关于无限分类的问题

    通过这种方式,改进的前序遍历树模型能够有效地处理无限分类的问题,提供了快速的查询速度和灵活的树操作。虽然在插入和删除节点时需要更新左值和右值,但相比于邻接列表模型的效率提升,这些额外的工作是值得的。在...

    无限级分类----改进前序遍历树

    总结来说,"无限级分类----改进前序遍历树"是通过二叉树数据结构来实现无限级别的分类管理,并使用改进的前序遍历方法来访问所有节点。在C#中,我们可以自定义类来构建二叉树,并通过递归函数进行遍历。这个过程涉及...

    C语言实现二叉树的前序遍历(非递归)

    通过以上分析,我们可以看到,非递归的前序遍历不仅避免了递归可能带来的栈溢出问题,而且通过巧妙利用栈的数据结构,能够有效地实现二叉树的遍历。这一方法在处理大规模数据或深度较大的二叉树时尤为有用。在实际...

    树的前序遍历

    树的遍历是理解和操作树的关键技术之一,其中前序遍历是最基础且常用的方法。本文将深入探讨树的前序遍历算法,并结合JAVA脚本和SQL这两种不同的编程语言进行说明。 首先,我们要理解什么是树的前序遍历。前序遍历...

    按前序遍历创建二叉树

    在这个问题中,我们要讨论的是如何通过前序遍历的序列来构建一棵二叉树。 前序遍历(Preorder Traversal)是二叉树遍历的一种方法,它按照“根-左-右”的顺序访问每个节点。即首先访问根节点,然后递归地访问左子树...

    二叉树遍历--前序遍历

    本话题主要关注的是“前序遍历”,这是一种重要的遍历策略,常用于复制或打印树的结构。 **前序遍历**(Preorder Traversal)的规则如下: 1. 首先访问根节点。 2. 然后递归地访问左子树。 3. 最后递归地访问右子...

    已知中序遍历和后序遍历求前序遍历

    已知中序遍历和后序遍历,求前序遍历。有比较详尽的中文注释。

    二叉树的前序遍历

    1. **前序遍历**:访问根节点 -&gt; 遍历左子树 -&gt; 遍历右子树。 2. **中序遍历**:遍历左子树 -&gt; 访问根节点 -&gt; 遍历右子树。 3. **后序遍历**:遍历左子树 -&gt; 遍历右子树 -&gt; 访问根节点。 #### 三、代码解析 本段...

    二叉树已知后序和中序遍历求前序遍历,C++代码

    二叉树已知后序和中序遍历求前序遍历,C++编写已通过编译

    二叉树前序遍历

    运行成功的二叉树 前序遍历 自己编写的程序 运行无错误

    C语言二叉树的前序遍历程序及实验报告

    实验分析显示,递归是构建和遍历二叉树的一种强大工具,而栈则用于处理非线性遍历的问题,尤其是在前序遍历中处理右子树。通过这次实验,我们可以深入理解递归算法的工作原理以及数据在栈中的进出过程,这对理解...

    二叉树前序遍历后续遍历,二叉树转换为树的算法

    前序遍历是一种访问二叉树节点的方式,其顺序是:根节点 -&gt; 左子树 -&gt; 右子树。这种遍历方式常用于复制或打印一棵树的结构。例如,对于以下二叉树: ``` 1 / \ 2 3 / \ / \ 4 5 6 7 ``` 前序遍历的结果是:`1 ...

    python实现二叉树的创建、前序遍历、中序遍历以及层次遍历

    本教程将深入探讨如何在Python中实现二叉树的创建、前序遍历、中序遍历以及层次遍历。 首先,我们来理解二叉树的链式存储结构。在Python中,我们可以用类来表示二叉树的节点,每个节点包含一个值、一个指向左子节点...

    C++数据结构代码——层序前序遍历

    C++数据结构代码——层序前序遍历 本节主要介绍了 C++ 数据结构中二叉树的层序前序遍历算法的实现。该算法的主要功能是建立二叉树,并实现层序遍历和先序遍历的功能。 二叉树的数据结构 在本节的代码中,我们定义...

    关于软考-二叉树遍历问题总结前序遍历、后序遍历、中序遍历、递归遍历

    前序遍历首先访问根节点,然后递归地遍历左子树,最后遍历右子树。递归实现时,首先访问根节点,然后对左右子树进行前序遍历。非递归实现通常借助栈来完成,先将根节点压栈,然后依次处理栈顶的右子节点和左子节点。...

    树的双亲表示法的前序遍历

    在处理具体问题时,例如压缩包中的“tree”文件,我们可能需要将文件内容解析成一棵树结构,然后应用上述的双亲表示法和前序遍历算法。这可能涉及到读取文件、解析节点信息、构建树结构、以及实现遍历算法的过程。...

    二叉树的基本操作,前序遍历,中序遍历,后序遍历,层序遍历

    前序遍历也称为根-左-右遍历,按照访问顺序为:先访问根节点,然后遍历左子树,最后遍历右子树。递归实现前序遍历的伪代码如下: ``` function preOrder(node): if node is not null: visit(node) preOrder(node...

    二叉树的前序遍历.docx

    3. **递归右子树**:如果当前节点的右子树不为空,则递归调用前序遍历函数处理右子树。 递归方法的Java代码实现如下: ```java import java.util.ArrayList; import java.util.List; class TreeNode { int val; ...

    二叉树的遍历,前序遍历 中序遍历 后序遍历

    前序遍历的顺序是:根节点 -&gt; 左子树 -&gt; 右子树。这意味着我们首先访问根节点,然后递归地遍历左子树,最后遍历右子树。如果节点没有子树,那么就直接跳过。这种遍历方式常用于复制整个二叉树或者打印出树的结构。 ...

    非递归前序遍历 数据结构

    在实际应用中,非递归前序遍历常用于数据结构的序列化、树的复制以及树的遍历性质检查等场景。 通过学习和理解非递归前序遍历,我们可以更好地掌握数据结构的精髓,提升解决问题的能力。同时,这也是算法设计和分析...

Global site tag (gtag.js) - Google Analytics