PHP中用于遍历树的数据结构?

8

我没有计算机科学或数据结构的背景。我想制作一个PHP类来存储修改过的先序遍历树,以便与数据库同步并进行操作。

基本上,我需要存储以下数据:

+-------------+----------------------+-----+-----+
| category_id | name                 | lft | rgt |
+-------------+----------------------+-----+-----+
|           1 | ELECTRONICS          |   1 |  20 |
|           2 | TELEVISIONS          |   2 |   9 |
|           3 | TUBE                 |   3 |   4 |
|           4 | LCD                  |   5 |   6 |
|           5 | PLASMA               |   7 |   8 |
|           6 | PORTABLE ELECTRONICS |  10 |  19 |
|           7 | MP3 PLAYERS          |  11 |  14 |
|           8 | FLASH                |  12 |  13 |
|           9 | CD PLAYERS           |  15 |  16 |
|          10 | 2 WAY RADIOS         |  17 |  18 |
+-------------+----------------------+-----+-----+

我曾考虑使用数组,但似乎有些繁琐。如果是像这样的二维数组:array( 'name'=> "PORTABLE ELECTRONICS", 'lft' => 10, 'rgt' = 19 ),那么反复遍历该数组以确保所有数字都存在等操作会变得很麻烦。

由于PHP提供了一些新的数据结构,我想知道是否有任何这些数据结构可以比使用数组更好?

  • SplDoubly
  • LinkedList
  • SplStack
  • SplQueue
  • SplHeap
  • SplMaxHeap
  • SplMinHeap
  • SplPriorityQueue
  • SplFixedArray
  • SplObjectStorage

编辑: 这个类不会成为一个存储在数据库表中的树形结构的网关。(如果是这样的话,我只需要查询类。)它只是一个单独的mmpt,存储在某种PHP数据结构中。


1
(资源) http://matthewturland.com/2010/05/20/new-spl-features-in-php-5-3/ - Gordon
2
嘿,实际上不是邻接模型,而是嵌套集模型。我认为这些新的SPL都没有帮助,因为_node_本身必须知道它的邻居,而不仅仅是父节点。我会让一堆对象引用左右对象,并使用一种“组合”模式来级联更改。 - Wrikken
1
可能值得一看[Doctrine] [http://www.propelorm.org/browser/branches/1.6/generator/lib/behavior/nestedset]和[Propel] [http://trac.doctrine-project.org/browser/branches/1.2/lib/Doctrine/Node]如何处理嵌套集。尤其是使用Propel,你可能需要构建一些虚拟模型来仔细研究代码。 - prodigitalson
删除数据成员 - unset($array[$idVar]); - b_dubb
@b_dubb 好的,问题解决了一半! :D - user151841
显示剩余2条评论
3个回答

8
编辑:好的,我进一步研究了一下。我认为术语混淆了。您不是在寻找 PHP 中的遍历树的数据结构。您想要在 PHP 中使用树作为数据结构,并且您想使用称为 修改的先序树遍历算法 的方法从该树中恢复数据。

引用:

在处理树时,我们从左到右逐层向下工作,首先降到每个节点的子节点,然后分配右侧编号并继续向右移动。这种方法称为修改的先序树遍历算法。


这篇文章是关于在PHP和MySQL中存储分层数据的比较。在PHP中,我们可以使用一个简单的树形结构。但问题在于,将树形结构存储在扁平化的MySQL数据库中并不容易。一种解决方法是从PHP中提取出邻接表。这本质上是每个项目及其父项的列表。但这种做法有一些缺点。
另一种方法是从PHP树中提取描述分层数据所需的嵌套集信息。要从PHP树中获取此信息,我们需要使用“修改的先序遍历算法”。这是一种按顺序上下遍历树以从中提取特定信息的方法。
无论我们使用邻接表模型还是修改的先序遍历来检索信息,我们都使用相同的PHP树。区别在于我们如何从树中检索信息以及如何将信息存储在MySQL中。从MySQL中提取信息的代码已经在引用页面上了。要在PHP和MySQL之间同步数据,只需使用该页面上描述的MySQL技术和PHP树类即可。
为此,我创建了一个PHP类来存储一棵树。它使用节点。可以将每个节点视为完整树的根,因为可以从每个节点访问完整子树。将节点与树分开更容易,并且会减少开销。
该类的重要部分是showAdjacency方法。它使用修改的前序遍历树来运行树,并显示每个名称的lft和rgt数量,使您可以将数据存储在MySQL中作为嵌套集。
您还可以显示树,以便可视化它。该类中缺少删除方法。当您实现它时,必须将已删除节点的子项传递给节点的父项。也许我以后会做这个。
我将在帖子底部包含整个类,但是以下是如何检索修改过的前序遍历树的数据:
      // The modified preorder tree traversal.
    private function showAdjacency($root, $spaces)
    {
          // Print the data first
        if ($root)
        {
              // On the way down the tree, we get lft.
            $left = ++$spaces;                
            foreach( $root->next as $child)
            {                    
                if ($child)
                {
                    $spaces = $this->showAdjacency($child, $spaces);                      
                }
            }
        }
          // On the way back up, we get rgt.
        $right = ++$spaces;
        echo "$left - $right - $root->data <br/>";                
        return $spaces;
    }

你可以显然地将$root->data、$rgt和$lft存储在一个数组中,该数组用于与数据库同步。

这是整个类。在类之后,我使用你链接的页面中的示例数据创建了一棵树,并输出了lft和rgt值以及树形可视化。

你可以在Codepad上运行代码

<?php  
  // Class defintions and methods:
  // It's easier to use separate nodes. Each node still points to an entire and complete subtree.
class Node
{
    public $data;
    public $next = array();
}

  // A first try at creating a tree with any number of branches from its nodes
  // by Peter Ajtai - feel free to use and modify
class Tree
{
    // The root
    private $root;
    public function __construct()
    {
        $this->root = NULL;
    }

      // The public wrapper. 
      // This is needed because we must start the recursive functions
      // at the root, and we do not want to give public access to root.
      // I am not familiar w overloading in PHP, maybe __set should be used for this
    public function insertPub($data, $parent)
    {
        $root =& $this->root;
        $this->insert($root, $data, $parent);
    }

    private function insert(&$root, $data, $parent)
    {

          // Create the root of the entire tree if no parent is passed in        
        if (!$root && !$parent)
        {
            $root = new Node;
            $temp =& $root;
            $temp->data = $data;
            // Create space for next insertion
            $temp->next[] = NULL;                     
        } else if ($root->data == $parent)
        {

              // Add data as a child if we're at the parent, and we're done.
              // Find the empty node to insert at
            foreach($root->next as &$child)
            {
                  // Insert at the first (and only) NULL
                if (!$child)
                {
                    $child = new Node;
                    $temp =& $child;
                    $temp->data = $data;                        
                    // Create space for next insertion
                    $temp->next[] = NULL;
                }
            }
              // Create space for the next insertion
            $root->next[] = NULL;
        } else
        {
              // Keep searching for the parent as default behavior.
            foreach($root->next as $child)
            {
                if ($child)
                {
                    $this->insert($child, $data, $parent);
                }
            }
        }
    }
      // Public wrapper for the display function.
    public function showAdjPub()
    {
        echo "The neighbors:<br/><br/>";
        $root =& $this->root;
        $this->showAdjacency($root, 0);
        echo "<br/>";
    }

      // The display function.
    private function showAdjacency($root, $spaces)
    {
          // Print the data first
        if ($root)
        {
            $left = ++$spaces;                
            foreach( $root->next as $child)
            {                    
                if ($child)
                {
                    $spaces = $this->showAdjacency($child, $spaces);                      
                }
            }
        }
        $right = ++$spaces;
        echo "$left - $right - $root->data <br/>";                
        return $spaces;
    }
      // Public wrapper for the display function.
    public function showAllPub()
    {
        echo "The tree:<br/><br/>";
        $root =& $this->root;
        $this->showAll($root, 0);
        echo "<br/>";
    }

      // The display function.
    private function showAll($root, $spaces)
    {
          // Print the data first
        if ($root)
        {
            for ($i=0; $i < $spaces; ++$i)
                echo "---> ";
            echo "$root->data <br/>";
            ++$spaces;
            foreach( $root->next as $child)
            {                    
                if ($child)
                {
                    $this->showAll($child, $spaces);
                }
            }
        }
    }        
}

  // Create a new instance of the tree
$myTree = new Tree;

  // Insert the data into the tree.    
  // The following data would be retrieved using MySQL
$name = 'Electronics'; $parent = NULL;
$myTree->insertPub($name, $parent);
$name = 'Televisions'; $parent = 'Electronics';
$myTree->insertPub($name, $parent);
$name = 'Tube'; $parent = 'Televisions';
$myTree->insertPub($name, $parent);    
$name = 'Lcd'; $parent = 'Televisions';
$myTree->insertPub($name, $parent); 
$name = 'Plasma'; $parent = 'Televisions';
$myTree->insertPub($name, $parent);    
$name = 'Portable Electronics'; $parent = 'Electronics';
$myTree->insertPub($name, $parent);    
$name = 'MP3 Players'; $parent = 'Portable Electronics';
$myTree->insertPub($name, $parent);    
$name = 'Flash'; $parent = 'MP3 Players';
$myTree->insertPub($name, $parent);    
$name = 'CD Players'; $parent = 'Portable Electronics';
$myTree->insertPub($name, $parent);    
$name = '2 Way Radios'; $parent = 'Portable Electronics';
$myTree->insertPub($name, $parent);    

  // Display adjacency
$myTree->showAdjPub();

  // Show hierarchy    
$myTree->showAllPub();      
?>

PS:像你建议的在PHP中使用嵌套数组存储数据会非常困难。在上面的类中,如果删除了一个数据成员,则树将被修改(包括整个子树的添加等),但是lftrgt值仍将被正确检索。

如果使用数组存储信息,则删除既有父级又有子级的项目将非常困难,并且更新lft和rgt值也会非常困难。最后,将大量集合(子树)添加到数组中也将非常困难。

树实际上是存储此类分层数据的理想方式。它模仿了我们集合的概念。问题在于,虽然PHP轻松存储树,但MySQL不支持,因此我们需要通过修改先序遍历树来从PHP树中提取信息,以便将其存储在MySQL数据库中。


1
一个复合结构不是更适合表示节点等于树吗? - Wrikken
@Wrikken,这正是我想的 - 会有一个接受树的addNode()功能。 - user151841
@Wrikken - 不使用单独的节点实际上更加麻烦。每个节点实际上是一个单独子树的根。拥有许多节点实例比Tree类更容易。但从理论上讲,它们本质上是相同的。 - Peter Ajtai
@user - 好的,这应该可以让您与MySQL数据同步。我重写了它,以便输出显示与您在原帖中和mysql文章描述的数据相同。 - Peter Ajtai

1

一个使用Node和Tree对象的简单运行程序。不多说了,女士们,先生们,以下是代码:

<?php
#Create a node class
#-----------------------------------------------------------------------------
class Node
{
    #The node properties
    public $value;
    public $leftChild;
    public $rightChild;

    #Set the properties for the node
    public function set_value($passedValue)
    {
        $this->value = $passedValue;
    }

    public function set_left_child($passedChild)
    {
        $this->leftChild = $passedChild;
    }

    public function set_right_child($passedChild)
    {
        $this->rightChild = $passedChild;
    }

    #Get the properties for the node
    public function get_value()
    {
        return $this->value;
    }

    public function get_left_child()
    {
        return $this->leftChild;
    }

    public function get_right_child()
    {
        return $this->rightChild;
    }
}





#Create a tree class with a function to transverse the node
#-----------------------------------------------------------------------------
class Tree
{
    #The node properties
    public $treeNodes = array();
    public $preorderedNodes = array();
    public $nodeArray = array();

    #Set the tree nodes from the passed array
    public function set_tree_nodes($nodeArray)
    {
        $this->nodeArray = $nodeArray;
        #Set each node with its children based on the passed array
        foreach($this->nodeArray AS $node) array_push($this->treeNodes, $this->set_node_object($node));
    }


    public function set_node_object($node)
    {
        $nodeObject = new Node();
        $nodeObject->set_value($node['value']);
        if(!empty($node['left_child'])) $nodeObject->set_left_child($this->nodeArray[$node['left_child']]);
        if(!empty($node['right_child'])) $nodeObject->set_right_child($this->nodeArray[$node['right_child']]);

        return $nodeObject;
    }

    #perfom a preorder transversal on the tree and return the tree nodes in the expected order
    public function do_preorder_transversal($node)
    {
        #If the node is empty, end the recursion
        if(empty($node)) return $this->preorderedNodes;
        #Start the transversal
        if($node == 'head' && !empty($this->treeNodes[0])) $node=$this->treeNodes[0]; 

        #Add the node to the pre-ordered node list
        array_push($this->preorderedNodes, $node);

        $leftChild = $node->get_left_child(); 
        $rightChild = $node->get_right_child(); 
        if(!empty($leftChild)) $this->do_preorder_transversal($this->set_node_object($leftChild));
        if(!empty($rightChild)) $this->do_preorder_transversal($this->set_node_object($rightChild));
    }


    #Get the preordered nodes
    public function get_preordered_nodes()
    {
        return $this->preorderedNodes;
    }
}









#-----------------------------------------------------------------------------
# Test the objects
#-----------------------------------------------------------------------------
#Call the class in an example
$sampleTree = new Tree();

$seedData = array();
#Seed data array is in the format [value, [key to left child], [key to right child]]
$seedData[0] = array('value'=>2, 'left_child'=>1, 'right_child'=>2);
$seedData[1] = array('value'=>7, 'left_child'=>3, 'right_child'=>4);
$seedData[2] = array('value'=>5, 'left_child'=>'', 'right_child'=>7);
$seedData[3] = array('value'=>2, 'left_child'=>'', 'right_child'=>'');
$seedData[4] = array('value'=>6, 'left_child'=>5, 'right_child'=>6);
$seedData[5] = array('value'=>5, 'left_child'=>'', 'right_child'=>'');
$seedData[6] = array('value'=>11, 'left_child'=>'', 'right_child'=>'');
$seedData[7] = array('value'=>9, 'left_child'=>8, 'right_child'=>'');
$seedData[8] = array('value'=>4, 'left_child'=>'', 'right_child'=>'');

$sampleTree->set_tree_nodes($seedData);
#Create the root node to start the tree transversal
$sampleTree->do_preorder_transversal('head');

#Now get the preordered nodes and print out their values
$preordered = $sampleTree->get_preordered_nodes();
foreach($preordered AS $count=>$node) echo "<BR>[".$count."] ".$node->get_value();
?>

以下是结果:

[0] 2

[1] 7

[2] 2

[3] 6

[4] 5

[5] 11

[6] 5

[7] 9

[8] 4


0

这是我在PHP中使用的构建二叉树及其操作的代码:

  <?php
class Node
{
 public $data;
 public $leftChild;
 public $rightChild;

 public function __construct($data)
  {
   $this->data=$data;
   $this->leftChild=null;
   $this->rightChild=null;
  }
 public function disp_data()
  {
   echo $this->data;
  }


}//end class Node
class BinaryTree
{
 public $root;
 //public $s;
 public function __construct()
  {
   $this->root=null;
   //$this->s=file_get_contents('store');

  }
//function to display the tree
  public function display()
  {
   $this->display_tree($this->root);

  }
  public function display_tree($local_root)
  {

   if($local_root==null) 
     return;
    $this->display_tree($local_root->leftChild);
    echo $local_root->data."<br/>";
    $this->display_tree($local_root->rightChild);

  } 
// function to insert a new node
  public function insert($key)
   {
    $newnode=new Node($key);
      if($this->root==null)
        {
         $this->root=$newnode;
         return;
        }
      else
        {
         $parent=$this->root;
         $current=$this->root;
           while(true)
             {
               $parent=$current;
                 //$this->find_order($key,$current->data);
                if($key==($this->find_order($key,$current->data)))
                  {
                      $current=$current->leftChild;
                       if($current==null)
                         {
                          $parent->leftChild=$newnode;
                          return;
                         }//end if2
                  }//end if1 
                else
                  {
                      $current=$current->rightChild;
                       if($current==null)
                         {
                          $parent->rightChild=$newnode;
                          return;  
                         } //end if1                       
                  } //end else
             }//end while loop 
        }//end else

   } //end insert function

//function to search a particular Node
 public function find($key)
  {
    $current=$this->root;
     while($current->data!=$key)
          {
            if($key==$this->find_order($key,$current->data))
              {
                $current=$current->leftChild;
              }
            else
              {
                $current=$current->rightChild;
              }
            if($current==null)
              return(null);

          }
         return($current->data); 
  }// end the function to search
 public function delete1($key)
  {
    $current=$this->root;
    $parent=$this->root;

    $isLeftChild=true;
     while($current->data!=$key)
          {
           $parent=$current;
           if($key==($this->find_order($key,$current->data)))
             {
              $current=$current->leftChild;
              $isLeftChild=true;
             }   
           else
             {
              $current=$current->rightChild;
              $isLeftChild=false;   
             } 
            if($current==null)
              return(null);
          }//end while loop 

      echo "<br/><br/>Node to delete:".$current->data;
     //to delete a leaf node 
     if($current->leftChild==null&&$current->rightChild==null)
       {
           if($current==$this->root)
              $this->root=null;  
          else if($isLeftChild==true)
           {
            $parent->leftChild=null;
           }  
         else
           {
            $parent->rightChild=null;
           }
         return($current);       
       }//end if1
     //to delete a node having a leftChild 
   else if($current->rightChild==null)
       {
          if($current==$this->root)
           $this->root=$current->leftChild;
          else if($isLeftChild==true)
           {
            $parent->leftChild=$current->leftChild;
           }
          else
           {
            $parent->rightChild=$current->leftChild;
           }   
          return($current);
       }//end else if1
    //to delete a node having a rightChild
   else if($current->leftChild==null)
       {
         if($current==$this->root)
           $this->root=$current->rightChild;
         else if($isLeftChild==true)
           {
            $parent->leftChild=$current->rightChild;
           }  
         else
           {
            $parent->rightChild=$current->rightChild; 
           }  
           return($current);
       }  
   //to delete a node having both childs
    else
       {
        $successor=$this->get_successor($current);
        if($current==$this->root)
          {
            $this->root=$successor; 

          }
        else if($isLeftChild==true)
          {
           $parent->leftChild=$successor;
          }
        else
          {
           $parent->rightChild=$successor;
          }     
         $successor->leftChild=$current->leftChild;
        return($current);
       }   


  }//end the function to delete a node
//Function to find the successor node
 public function get_successor($delNode)
  {
   $succParent=$delNode;
   $successor=$delNode;
   $temp=$delNode->rightChild;
    while($temp!=null)
         {
          $succParent=$successor;
          $successor=$temp;
          $temp=$temp->leftChild;
         }
   if($successor!=$delNode->rightChild)
     {
      $succParent->leftChild=$successor->rightChild;
      $successor->rightChild=$delNode->rightChild;
     }
  return($successor);
  }
//function to find the order of two strings
 public function find_order($str1,$str2)
  {
     $str1=strtolower($str1);
     $str2=strtolower($str2);
     $i=0;
     $j=0;

     $p1=$str1[i];
     $p2=$str2[j]; 
  while(true)
   {  
       if(ord($p1)<ord($p2)||($p1==''&&$p2==''))
         {

           return($str1);
         }
      else
         {
           if(ord($p1)==ord($p2))
             {
              $p1=$str1[++$i];
              $p2=$str2[++$j];
              continue;
             }
          return($str2); 
         }
   }//end while

  } //end function find string order

 public function is_empty()
  {
    if($this->root==null)
      return(true);
    else
      return(false);
  }
}//end class BinaryTree
?>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接