2010-08-05 15 views
6

No tengo experiencia en CS o estructuras de datos. Quiero hacer una clase de PHP que almacene un modified preorder transversal tree, para manipular y sincronizar con una base de datos.estructura de datos para árbol transversal en PHP?

Básicamente necesito para almacenar datos como:

+-------------+----------------------+-----+-----+ 
| 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 | 
+-------------+----------------------+-----+-----+ 

Yo estaba pensando en utilizar una matriz, pero parece engorroso. Si fuera una matriz de matrices como esta: array('name'=> "PORTABLE ELECTRONICS", 'lft' => 10, 'rgt' = 19), entonces sería engorroso recorrer esa matriz repetidamente para asegurarse de que todos los números están presentes, etc.

Dado que PHP tiene algunas nuevas estructuras de datos disponibles, me pregunto si cualquiera de estos me daría algún beneficio sobre el uso de una matriz?

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

Editar: Esta clase no va a ser una puerta de entrada a un árbol almacenado en una tabla de base de datos. (Si lo fuera, solo tendría una consulta de clases.) Es solo una mmpt independiente en algún tipo de estructura de datos PHP.

+1

* (recurso) * http://matthewturland.com/2010/05/20/new-spl-features-in-php-5-3/ – Gordon

+2

Heh, de hecho no la adyacencia sino el modelo de conjunto anidado. No creo que ninguno de estos nuevos SPL ayude, ya que el _nodo_ en sí mismo más sabe de sus vecinos, no solo de los padres. Simplemente tendría un montón de objetos que tienen una referencia a los objetos a la izquierda y a la derecha, con un tipo de patrón "Compuesto" para hacer cambios en cascada. – Wrikken

+1

Puede valer la pena echarle un vistazo a cómo [Doctrine] [http://www.propelorm.org/browser/branches/1.6/generator/lib/behavior/nestedset] y [Propel] [http: //trac.doctrine -project.org/browser/branches/1.2/lib/Doctrine/Node] manipular conjuntos anidados. Es posible que necesite construir algunos modelos ficticios para obtener una buena visión del código, especialmente con Propel. – prodigitalson

Respuesta

7

Edit: Bien, investigué esto un poco más. Creo que hubo una confusión en la nomenclatura. No estás buscando un data structure for a transveral tree en PHP. Desea utilizar un árbol como estructura de datos en PHP y desea recuperar datos de ese árbol mediante un método llamado modified preorder tree traversal algorithm.

Quoting:

When working with a tree, we work from left to right, one layer at a time, descending to each node's children before assigning a right-hand number and moving on to the right. This approach is called the modified preorder tree traversal algorithm.


Esto es sobre el almacenamiento de datos jerárquicos en PHP vs MySQL. En PHP podemos usar un árbol simple. El problema es que no es fácil almacenar un árbol en la base de datos plana que es MySQL. Una opción es tomar de allí la lista PHP y de recuperación y adyacencia. Esto es esencialmente una lista de cada elemento y sus padres. Esta forma de hacer las cosas tiene algunos inconvenientes.

Otro método es extraer información del árbol de PHP que describe los conjuntos anidados que se pueden crear a partir de los datos jerárquicos. Para obtener esta información del árbol PHP, necesitamos usar un algoritmo de cruce de árbol preordenador modificado. Este es un método para subir y bajar el árbol para extraer cierta información del mismo.

Si usamos el modelo de lista de adyacencia o el recorrido de árbol de preordenamiento modificado para recuperar la información, utilizamos exactamente el mismo árbol de PHP. La diferencia es cómo recuperamos la información del árbol y cómo almacenamos la información en MySQL.El código de cómo extraer la información de MySQL ya está en the page you quoted. Para sincronizar los datos entre PHP y MySQL solo tienes que usar las técnicas de MySQL descritas en esa página y una clase de árbol PHP.

Para esto, creé una clase en PHP que almacena un árbol. Utiliza un nodo. Cada nodo se puede considerar como la raíz de un árbol completo, ya que desde cada nodo se puede acceder a un subárbol completo. Era más fácil separar el nodo del árbol y causa menos sobrecarga.

La parte importante de la clase es showAdjacency método. Esto ejecuta el árbol utilizando un cruce de árbol de preordenamiento modificado, y muestra lft y rgt cantidad para cada nombre que le permite almacenar los datos en MySQL como un conjunto anidado.

También puede mostrar el árbol para que pueda visualizarlo. El método de eliminación falta en esta clase. Cuando lo implemente, debe pasar los elementos secundarios del nodo eliminado al elemento primario del nodo. Quizás lo haga más tarde.

Voy a incluir a toda la clase en la parte inferior del poste, pero aquí es cómo se recuperan los datos para el recorrido del árbol preorden modificación:

 // 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; 
    } 

Puede almacenar obviamente $ Raíz> datos, $ rgt, y $ lft en una matriz que usa para sincronizar con su base de datos.

Aquí está toda la clase. Después de la clase, creo un árbol con los datos de muestra del the page you linked to y obtengo los valores de lft y rgt, así como la visualización del árbol.

You can run the code on 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: almacenar los datos en PHP en matrices anidadas como usted sugirió que sería muy difícil. En la clase anterior, si se elimina un miembro de datos, se modifica el árbol (incluidas las adiciones de subárboles enteros, etc.) los valores lft y rgt se recuperarán correctamente.

Si utiliza matrices para almacenar la información, le resultará extremadamente difícil eliminar elementos que tengan padres e hijos, y la actualización de lft y rgt valuse sería muy difícil. Finalmente, agregar conjuntos grandes (subárboles) a la matriz también sería extremadamente difícil.

Un árbol es realmente la forma ideal de almacenar este tipo de datos jerárquicos. Se imita nuestra noción de conjuntos. El problema es que mientras PHP almacena árboles fácilmente, MySQL no lo hace, así que tenemos que pasar por todo el trabajo difícil del recorrido de árbol de preordenamiento modificado para extraer información del árbol PHP para que podamos almacenarlo en MySQL db.

+1

¿No sería más apropiado un compuesto así que un nodo === Árbol? – Wrikken

+0

@Wrikken eso es exactamente lo que estaba pensando: que habría una funcionalidad addNode() que acepta un árbol. – user151841

+0

@Wrikken - En realidad, es más doloroso pero no usar un nodo separado. Cada nodo es en realidad la raíz de un subárbol separado. Es más fácil tener un montón de instancias del nodo que de la clase arbol. Pero teóricamente son esencialmente idénticos. –

0

Este es el código que utilicé para construir un árbol binario y sus operaciones en 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 
?> 
1

Un programa en ejecución sencilla con el Nodo y objetos de árbol.Sin más preámbulos, señoras y caballeros, aquí está el código:

<?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(); 
?> 

Los resultados son como sigue:

[0] 2

[1] 7

[2] 2

[3] 6

[4] 5

[5] 11

[6] 5

[7] 9

[8] 4

Cuestiones relacionadas