将一系列父子关系转化为层次树?

Posted

技术标签:

【中文标题】将一系列父子关系转化为层次树?【英文标题】:Convert a series of parent-child relationships into a hierarchical tree? 【发布时间】:2011-02-24 08:05:23 【问题描述】:

我有一堆名称-父名称对,我希望将它们变成尽可能少的层次树结构。例如,这些可能是配对:

Child : Parent
    H : G
    F : G
    G : D
    E : D
    A : E
    B : C
    C : E
    D : NULL

需要转化为(a)层次树:

D
├── E
│   ├── A
│   │   └── B
│   └── C   
└── G
    ├── F
    └── H

我想要的最终结果是一组嵌套的 <ul> 元素,每个 <li> 都包含孩子的名字。

配对中没有不一致(孩子是它自己的父母,父母是孩子的孩子等),因此可能可以进行一系列优化。

php 中,我如何从包含子 => 父对的数组转到一组嵌套的 <ul>s?

我感觉涉及到递归,但我还没有清醒到想通。

【问题讨论】:

【参考方案1】:

这需要一个非常基本的递归函数将子/父对解析为树结构,并需要另一个递归函数将其打印出来。只有一个功能就足够了,但为了清楚起见,这里有两个(可以在此答案的末尾找到一个组合功能)。

首先初始化子/父对数组:

$tree = array(
    'H' => 'G',
    'F' => 'G',
    'G' => 'D',
    'E' => 'D',
    'A' => 'E',
    'B' => 'C',
    'C' => 'E',
    'D' => null
);

然后是将该数组解析为层次树结构的函数:

function parseTree($tree, $root = null) 
    $return = array();
    # Traverse the tree and search for direct children of the root
    foreach($tree as $child => $parent) 
        # A direct child is found
        if($parent == $root) 
            # Remove item from tree (we don't need to traverse this again)
            unset($tree[$child]);
            # Append the child into result array and parse its children
            $return[] = array(
                'name' => $child,
                'children' => parseTree($tree, $child)
            );
        
    
    return empty($return) ? null : $return;    

还有一个函数遍历该树以打印出无序列表:

function printTree($tree) 
    if(!is_null($tree) && count($tree) > 0) 
        echo '<ul>';
        foreach($tree as $node) 
            echo '<li>'.$node['name'];
            printTree($node['children']);
            echo '</li>';
        
        echo '</ul>';
    

以及实际用法:

$result = parseTree($tree);
printTree($result);

这是$result的内容:

Array(
    [0] => Array(
        [name] => D
        [children] => Array(
            [0] => Array(
                [name] => G
                [children] => Array(
                    [0] => Array(
                        [name] => H
                        [children] => NULL
                    )
                    [1] => Array(
                        [name] => F
                        [children] => NULL
                    )
                )
            )
            [1] => Array(
                [name] => E
                [children] => Array(
                    [0] => Array(
                        [name] => A
                        [children] => NULL
                    )
                    [1] => Array(
                        [name] => C
                        [children] => Array(
                            [0] => Array(
                                [name] => B
                                [children] => NULL
                            )
                        )
                    )
                )
            )
        )
    )
)

如果您想要更高的效率,您可以将这些功能合二为一并减少迭代次数:

function parseAndPrintTree($root, $tree) 
    $return = array();
    if(!is_null($tree) && count($tree) > 0) 
        echo '<ul>';
        foreach($tree as $child => $parent) 
            if($parent == $root)                     
                unset($tree[$child]);
                echo '<li>'.$child;
                parseAndPrintTree($child, $tree);
                echo '</li>';
            
        
        echo '</ul>';
    

在这样小的数据集上,您只能节省 8 次迭代,但在更大的数据集上,它可能会有所作为。

【讨论】:

大图。如何将 printTree 函数更改为不直接回显树的 html,而是将所有输出 html 保存到变量中并返回它?谢谢 嗨,我认为函数声明必须是 parseAndPrintTree($tree, $root = null) 并且递归调用应该是 parseAndPrintTree($child, $tree);最好的问候【参考方案2】:

Yet another Function To Make A Tree(不涉及递归,使用引用代替):

$array = array('H' => 'G', 'F' => 'G', ..., 'D' => null);

function to_tree($array)

    $flat = array();
    $tree = array();

    foreach ($array as $child => $parent) 
        if (!isset($flat[$child])) 
            $flat[$child] = array();
        
        if (!empty($parent)) 
            $flat[$parent][$child] =& $flat[$child];
         else 
            $tree[$child] =& $flat[$child];
        
    

    return $tree;

返回一个像这样的分层数组:

Array(
    [D] => Array(
        [G] => Array(
            [H] => Array()
            [F] => Array()
        )
        ...
    )
)

使用递归函数可以轻松地将其打印为 HTML 列表。

【讨论】:

+1 - 非常聪明。虽然我发现递归解决方案更合乎逻辑。但我更喜欢你的函数的输出格式。 @Eric 更合乎逻辑?我不敢苟同。递归中没有任何“逻辑”。 OTOH 在解析递归函数/调用时会产生严重的认知开销。如果没有明确的堆栈分配,我会每天进行迭代而不是递归。 This solution is benchmarked below【参考方案3】:

$tree 中的平面结构转换为层次结构的另一种更简化的方法。公开它只需要一个临时数组:

// add children to parents
$flat = array(); # temporary array
foreach ($tree as $name => $parent)

    $flat[$name]['name'] = $name; # self
    if (NULL === $parent)
    
        # no parent, is root element, assign it to $tree
        $tree = &$flat[$name]; 
    
    else
    
        # has parent, add self as child    
        $flat[$parent]['children'][] = &$flat[$name];
    

unset($flat);

这就是将层次结构放入多维数组的全部内容:

Array
(
    [children] => Array
        (
            [0] => Array
                (
                    [children] => Array
                        (
                            [0] => Array
                                (
                                    [name] => H
                                )

                            [1] => Array
                                (
                                    [name] => F
                                )

                        )

                    [name] => G
                )

            [1] => Array
                (
                    [name] => E
                    [children] => Array
                        (
                            [0] => Array
                                (
                                    [name] => A
                                )

                            [1] => Array
                                (
                                    [children] => Array
                                        (
                                            [0] => Array
                                                (
                                                    [name] => B
                                                )

                                        )

                                    [name] => C
                                )

                        )

                )

        )

    [name] => D
)

如果您想避免递归(可能是大型结构的负担),输出就不那么简单了。

我一直想解决输出数组的 UL/LI “困境”。困境是,每个项目不知道孩子是否会跟进或需要关闭多少前面的元素。在另一个答案中,我已经通过使用RecursiveIteratorIterator 并寻找getDepth() 和我自己编写的Iterator 提供的其他元信息解决了这个问题:Getting nested set model into a &lt;ul&gt; but hiding “closed” subtrees。 answer 也表明,使用迭代器时您非常灵活。

但是,这是一个预先排序的列表,因此不适合您的示例。此外,我一直想用一种标准的树结构和 HTML 的 &lt;ul&gt;&lt;li&gt; 元素来解决这个问题。

我提出的基本概念如下:

    TreeNode - 将每个元素抽象为一个简单的TreeNode 类型,该类型可以提供它的值(例如Name)以及它是否有子元素。 TreeNodesIterator - 一个RecursiveIterator,能够遍历这些TreeNodes 的集合(数组)。这相当简单,因为 TreeNode 类型已经知道它是否有孩子以及哪些孩子。 RecursiveListIterator - 一个RecursiveIteratorIterator,它具有递归迭代任何类型的RecursiveIterator 时所需的所有事件: beginIteration / endIteration - 主列表的开始和结束。 beginElement / endElement - 每个元素的开始和结束。 beginChildren / endChildren - 每个子列表的开始和结束。 这个RecursiveListIterator 仅以函数调用的形式提供这些事件。子列表,因为它是典型的 &lt;ul&gt;&lt;li&gt; 列表,在其父 &lt;li&gt; 元素内打开和关闭。因此,endElement 事件在相应的endChildren 事件之后触发。这可以更改或可配置以扩大此类的使用。这些事件作为函数调用分发给装饰器对象,然后将它们分开。 ListDecorator - 一个“装饰器”类,它只是RecursiveListIterator 事件的接收者。

我从主输出逻辑开始。采用现在分层的$tree 数组,最终代码如下所示:

$root = new TreeNode($tree);
$it = new TreeNodesIterator(array($root));
$rit = new RecursiveListIterator($it);
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);

foreach($rit as $item)

    $inset = $decor->inset(1);
    printf("%s%s\n", $inset, $item->getName());

首先让我们看一下ListDecorator,它简单地包装了&lt;ul&gt;&lt;li&gt; 元素并决定如何输出列表结构:

class ListDecorator

    private $iterator;
    public function __construct(RecursiveListIterator $iterator)
    
        $this->iterator = $iterator;
    
    public function inset($add = 0)
    
        return str_repeat('  ', $this->iterator->getDepth()*2+$add);
    

构造函数采用它正在处理的列表迭代器。 inset 只是一个帮助函数,用于很好地缩进输出。其余的只是每个事件的输出函数:

    public function beginElement()
    
        printf("%s<li>\n", $this->inset());
    
    public function endElement()
    
        printf("%s</li>\n", $this->inset());
    
    public function beginChildren()
    
        printf("%s<ul>\n", $this->inset(-1));
    
    public function endChildren()
    
        printf("%s</ul>\n", $this->inset(-1));
    
    public function beginIteration()
    
        printf("%s<ul>\n", $this->inset());
    
    public function endIteration()
    
        printf("%s</ul>\n", $this->inset());
    

考虑到这些输出功能,这又是主要的输出总结/循环,我一步一步来:

$root = new TreeNode($tree);

创建将用于开始迭代的根TreeNode

$it = new TreeNodesIterator(array($root));

这个TreeNodesIterator 是一个RecursiveIterator,它可以在单个$root 节点上进行递归迭代。它作为数组传递,因为该类需要迭代一些东西并允许与一组子元素重用,这些子元素也是TreeNode 元素的数组。

$rit = new RecursiveListIterator($it);

这个RecursiveListIterator 是一个提供上述事件的RecursiveIteratorIterator。要使用它,只需要提供一个ListDecorator(上面的类)并分配addDecorator

$decor = new ListDecorator($rit);
$rit->addDecorator($decor);

然后一切都设置为仅foreach 并输出每个节点:

foreach($rit as $item)

    $inset = $decor->inset(1);
    printf("%s%s\n", $inset, $item->getName());

如本例所示,整个输出逻辑被封装在ListDecorator 类和单个foreach 中。整个递归遍历已经完全封装在 SPL 递归迭代器中,它提供了一个堆栈过程,这意味着内部没有进行递归函数调用。

基于事件的ListDecorator 允许您专门修改输出并为同一数据结构提供多种类型的列表。甚至可以更改输入,因为数组数据已封装到 TreeNode

完整代码示例:

<?php
namespace My;

$tree = array('H' => 'G', 'F' => 'G', 'G' => 'D', 'E' => 'D', 'A' => 'E', 'B' => 'C', 'C' => 'E', 'D' => null);

// add children to parents
$flat = array(); # temporary array
foreach ($tree as $name => $parent)

    $flat[$name]['name'] = $name; # self
    if (NULL === $parent)
    
        # no parent, is root element, assign it to $tree
        $tree = &$flat[$name];
    
    else
    
        # has parent, add self as child    
        $flat[$parent]['children'][] = &$flat[$name];
    

unset($flat);

class TreeNode

    protected $data;
    public function __construct(array $element)
    
        if (!isset($element['name']))
            throw new InvalidArgumentException('Element has no name.');

        if (isset($element['children']) && !is_array($element['children']))
            throw new InvalidArgumentException('Element has invalid children.');

        $this->data = $element;
    
    public function getName()
    
         return $this->data['name'];
    
    public function hasChildren()
    
        return isset($this->data['children']) && count($this->data['children']);
    
    /**
     * @return array of child TreeNode elements 
     */
    public function getChildren()
            
        $children = $this->hasChildren() ? $this->data['children'] : array();
        $class = get_called_class();
        foreach($children as &$element)
        
            $element = new $class($element);
        
        unset($element);        
        return $children;
    


class TreeNodesIterator implements \RecursiveIterator

    private $nodes;
    public function __construct(array $nodes)
    
        $this->nodes = new \ArrayIterator($nodes);
    
    public function  getInnerIterator()
    
        return $this->nodes;
    
    public function getChildren()
    
        return new TreeNodesIterator($this->nodes->current()->getChildren());
    
    public function hasChildren()
    
        return $this->nodes->current()->hasChildren();
    
    public function rewind()
    
        $this->nodes->rewind();
    
    public function valid()
    
        return $this->nodes->valid();
       
    public function current()
    
        return $this->nodes->current();
    
    public function key()
    
        return $this->nodes->key();
    
    public function next()
    
        return $this->nodes->next();
    


class RecursiveListIterator extends \RecursiveIteratorIterator

    private $elements;
    /**
     * @var ListDecorator
     */
    private $decorator;
    public function addDecorator(ListDecorator $decorator)
    
        $this->decorator = $decorator;
    
    public function __construct($iterator, $mode = \RecursiveIteratorIterator::SELF_FIRST, $flags = 0)
    
        parent::__construct($iterator, $mode, $flags);
    
    private function event($name)
    
        // event debug code: printf("--- %'.-20s --- (Depth: %d, Element: %d)\n", $name, $this->getDepth(), @$this->elements[$this->getDepth()]);
        $callback = array($this->decorator, $name);
        is_callable($callback) && call_user_func($callback);
    
    public function beginElement()
    
        $this->event('beginElement');
    
    public function beginChildren()
    
        $this->event('beginChildren');
    
    public function endChildren()
    
        $this->testEndElement();
        $this->event('endChildren');
    
    private function testEndElement($depthOffset = 0)
    
        $depth = $this->getDepth() + $depthOffset;      
        isset($this->elements[$depth]) || $this->elements[$depth] = 0;
        $this->elements[$depth] && $this->event('endElement');

    
    public function nextElement()
    
        $this->testEndElement();
        $this->event('nextElement');
        $this->event('beginElement');       
        $this->elements[$this->getDepth()] = 1;
     
    public function beginIteration()
    
        $this->event('beginIteration');
    
    public function endIteration()
    
        $this->testEndElement();
        $this->event('endIteration');       
    


class ListDecorator

    private $iterator;
    public function __construct(RecursiveListIterator $iterator)
    
        $this->iterator = $iterator;
    
    public function inset($add = 0)
    
        return str_repeat('  ', $this->iterator->getDepth()*2+$add);
    
    public function beginElement()
    
        printf("%s<li>\n", $this->inset(1));
    
    public function endElement()
    
        printf("%s</li>\n", $this->inset(1));
    
    public function beginChildren()
    
        printf("%s<ul>\n", $this->inset());
    
    public function endChildren()
    
        printf("%s</ul>\n", $this->inset());
    
    public function beginIteration()
    
        printf("%s<ul>\n", $this->inset());
    
    public function endIteration()
    
        printf("%s</ul>\n", $this->inset());
    



$root = new TreeNode($tree);
$it = new TreeNodesIterator(array($root));
$rit = new RecursiveListIterator($it);
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);

foreach($rit as $item)

    $inset = $decor->inset(2);
    printf("%s%s\n", $inset, $item->getName());

输出:

<ul>
  <li>
    D
    <ul>
      <li>
        G
        <ul>
          <li>
            H
          </li>
          <li>
            F
          </li>
        </ul>
      </li>
      <li>
        E
        <ul>
          </li>
          <li>
            A
          </li>
          <li>
            C
            <ul>
              <li>
                B
              </li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

Demo (PHP 5.2 variant)

一个可能的变体是迭代任何RecursiveIterator 并提供对所有可能发生的事件的迭代的迭代器。然后 foreach 循环中的 switch / case 可以处理这些事件。

相关:

Nested List with RecursiveArrayIterator The RecursiveIteratorIterator class

【讨论】:

像这个解决方案一样“精心设计”——它究竟是如何比前面的例子“更简化”的——它似乎是针对同一问题的过度设计解决方案 @Andre:按封装 IIRC 的等级。在另一个相关的答案中,我有一个完全非封装的代码片段,它要小得多,因此可能会“更简化”,具体取决于 POV。 @hakre 如何修改“ListDecorator”类以将“id”添加到从树数组中获取的 LI? @Gangesh:最容易使用节点访问者。 ^^ 开个玩笑,直接就是扩展装饰器并编辑beginElement(),得到内部迭代器(示例见inset()方法)和带有id属性的工作。 @hakre 谢谢。我会试试的。【参考方案4】:

虽然Alexander-Konstantinov 的解决方案一开始可能看起来不那么容易阅读,但它既天才又在性能方面呈指数级提升,这应该被评为最佳答案。

感谢朋友,我做了一个基准来比较这篇文章中提出的两种解决方案。

我有一个 @250k 平面树,需要转换 6 个级别,我正在寻找一种更好的方法来避免递归迭代。

递归与引用:

// Generate a 6 level flat tree
$root = null;
$lvl1 = 13;
$lvl2 = 11;
$lvl3 = 7;
$lvl4 = 5;
$lvl5 = 3;
$lvl6 = 1;    
$flatTree = [];
for ($i = 1; $i <= 450000; $i++) 
    if ($i % 3 == 0)   $lvl5 = $i; $flatTree[$lvl6] = $lvl5; continue; 
    if ($i % 5 == 0)   $lvl4 = $i; $flatTree[$lvl5] = $lvl4; continue; 
    if ($i % 7 == 0)   $lvl3 = $i; $flatTree[$lvl3] = $lvl2; continue; 
    if ($i % 11 == 0)  $lvl2 = $i; $flatTree[$lvl2] = $lvl1; continue; 
    if ($i % 13 == 0)  $lvl1 = $i; $flatTree[$lvl1] = $root; continue; 
    $lvl6 = $i;


echo 'Array count: ', count($flatTree), PHP_EOL;

// Reference function
function treeByReference($flatTree)

    $flat = [];
    $tree = [];

    foreach ($flatTree as $child => $parent) 
        if (!isset($flat[$child])) 
            $flat[$child] = [];
        
        if (!empty($parent)) 
            $flat[$parent][$child] =& $flat[$child];
         else 
            $tree[$child] =& $flat[$child];
        
    

    return $tree;


// Recursion function
function treeByRecursion($flatTree, $root = null)

    $return = [];
    foreach($flatTree as $child => $parent) 
        if ($parent == $root) 
            unset($flatTree[$child]);
            $return[$child] = treeByRecursion($flatTree, $child);
        
    
    return $return ?: [];


// Benchmark reference
$t1 = microtime(true);
$tree = treeByReference($flatTree);
echo 'Reference: ', (microtime(true) - $t1), PHP_EOL;

// Benchmark recursion
$t2 = microtime(true);
$tree = treeByRecursion($flatTree);
echo 'Recursion: ', (microtime(true) - $t2), PHP_EOL;

输出不言自明:

Array count: 255493
Reference: 0.3259289264679 (less than 0.4s)
Recursion: 6604.9865279198 (almost 2h)

【讨论】:

【参考方案5】:

嗯,首先我会将键值对的直接数组转换为分层数组

function convertToHeiarchical(array $input) 
    $parents = array();
    $root = array();
    $children = array();
    foreach ($input as $item) 
        $parents[$item['id']] = &$item;
        if ($item['parent_id']) 
            if (!isset($children[$item['parent_id']])) 
                $children[$item['parent_id']] = array();
            
            $children[$item['parent_id']][] = &$item;
         else 
            $root = $item['id'];
        
    
    foreach ($parents as $id => &$item) 
        if (isset($children[$id])) 
            $item['children'] = $children[$id];
         else 
            $item['children'] = array();
        
    
    return $parents[$root];

这可以将具有 parent_id 和 id 的平面数组转换为分层数组:

$item = array(
    'id' => 'A',
    'blah' => 'blah',
    'children' => array(
        array(
            'id' => 'B',
            'blah' => 'blah',
            'children' => array(
                array(
                    'id' => 'C',
                    'blah' => 'blah',
                    'children' => array(),
                ),
             ),
            'id' => 'D',
            'blah' => 'blah',
            'children' => array(
                array(
                    'id' => 'E',
                    'blah' => 'blah',
                    'children' => array(),
                ),
            ),
        ),
    ),
);

然后,只需创建一个渲染函数:

function renderItem($item) 
    $out = "Your OUtput For Each Item Here";
    $out .= "<ul>";
    foreach ($item['children'] as $child) 
        $out .= "<li>".renderItem($child)."</li>";
    
    $out .= "</ul>";
    return $out;

【讨论】:

【参考方案6】:

好吧,要解析成 UL 和 LI,应该是这样的:

$array = array (
    'H' => 'G'
    'F' => 'G'
    'G' => 'D'
    'E' => 'D'
    'A' => 'E'
    'B' => 'C'
    'C' => 'E'
    'D' => 'NULL'
);


recurse_uls ($array, 'NULL');

function recurse_uls ($array, $parent)

    echo '<ul>';
    foreach ($array as $c => $p)  
        if ($p != $parent) continue;
        echo '<li>'.$c.'</li>';
        recurse_uls ($array, $c);
    
    echo '</ul>';

但我很想看到一个不需要您如此频繁地遍历数组的解决方案...

【讨论】:

【参考方案7】:

这是我想出的:

$arr = array(
            'H' => 'G',
            'F' => 'G',
            'G' => 'D',
            'E' => 'D',
            'A' => 'E',
            'B' => 'C',
            'C' => 'E',
            'D' => null );

    $nested = parentChild($arr);
    print_r($nested);

    function parentChild(&$arr, $parent = false) 
      if( !$parent)  //initial call
         $rootKey = array_search( null, $arr);
         return array($rootKey => parentChild($arr, $rootKey));
      else  // recursing through
        $keys = array_keys($arr, $parent);
        $piece = array();
        if($keys)  // found children, so handle them
          if( !is_array($keys) )  // only one child
            $piece = parentChild($arr, $keys);
           else // multiple children
             foreach( $keys as $key )
               $piece[$key] = parentChild($arr, $key);
             
           
        else 
           return $parent; //return the main tag (no kids)
        
        return $piece; // return the array built via recursion
      
    

输出:

Array
(
    [D] => Array
        (
            [G] => Array
                (
                    [H] => H
                    [F] => F
                )

            [E] => Array
                (
                    [A] => A
                    [C] => Array
                        (
                            [B] => B
                        )    
                )    
        )    
)

【讨论】:

【参考方案8】:

父子关系嵌套Array 从数据库中获取所有记录并创建嵌套数组。

$data = SampleTable::find()->all();
$tree = buildTree($data);
print_r($tree);

public function buildTree(array $elements, $parentId = 0) 
    $branch = array();
    foreach ($elements as $element) 
        if ($element['iParentId'] == $parentId) 
            $children =buildTree($elements, $element['iCategoriesId']);
            if ($children) 
                $element['children'] = $children;
            
            $branch[] = $element;
        
    
    return $branch;

以json格式打印分类和子分类数据

public static function buildTree(array $elements, $parentId = 0)
    $branch = array();
    foreach($elements as $element)
        if($element['iParentId']==$parentId)
            $children =buildTree($elements, $element['iCategoriesId']);
            if ($children) 
                $element['children'] = $children;

            
                $branch[] = array(
                    'iCategoriesId' => $element->iCategoriesId,
                    'iParentId'=>$element->iParentId,
                    'vCategoriesName'=>$element->vCategoriesName,
                    'children'=>$element->children,
            );
        
    
    return[
        $branch
    ];

【讨论】:

【参考方案9】:
$tree = array(
    'H' => 'G',
    'F' => 'G',
    'G' => 'D',
    'E' => 'D',
    'A' => 'E',
    'B' => 'C',
    'C' => 'E',
    'D' => null,
    'Z' => null,
    'MM' =>'Z',
    'KK' =>'Z',
    'MMM' =>'MM',
    // 'MM'=>'DDD'
);

$aa=$this->parseTree($tree);

public function get_tress($tree,$key)


    $x=array();
    foreach ($tree as $keys => $value) 
        if($value==$key)
        $x[]=($keys);
        
    
    echo "<li>";
    foreach ($x as $ke => $val) 
    echo "<ul>";
        echo($val);
        $this->get_tress($tree,$val);
    echo "</ul>";
    
    echo "</li>";



function parseTree($tree, $root = null) 

    foreach ($tree as $key => $value) 
        if($value==$root)

            echo "<ul>";
            echo($key);
            $this->get_tress($tree,$key);
            echo "</ul>";
        
    

【讨论】:

【参考方案10】:

老问题,但我也不得不这样做,递归的例子让我头疼。在我的数据库中,我们有一个locations 表,它是一个loca_id PK(子)和自引用loca_parent_id(父)。

目的是在 HTML 中表示这种结构。 couyrse 的简单查询可以返回数据是一些固定的顺序,但我发现不足以以自然的方式显示这些数据。我真正想要的是使用 LEVEL 处理 Oracle 树遍历以帮助显示。

我决定使用“路径”的概念来唯一标识每个条目。例如:

按路径对数组进行排序应该更容易处理以进行有意义的显示。

我意识到使用关联数组和排序是作弊,因为它隐藏了操作的递归复杂性,但对我来说这看起来更简单:

<table>
<?php
    
    $sql = "
    
    SELECT l.*,
           pl.loca_name parent_loca_name,
           '' loca_path
    FROM locations l
    LEFT JOIN locations pl ON l.loca_parent_id = pl.loca_id
    ORDER BY l.loca_parent_id, l.loca_id
    
    ";
    
    function print_row ( $rowdata )
    
    ?>
                      <tr>
                          <td>
                              <?=$rowdata['loca_id']?>
                          </td>
                          <td>
                              <?=$rowdata['loca_path']?>
                          </td>
                          <td>
                              <?=$rowdata['loca_type']?>
                          </td>
                          <td>
                              <?=$rowdata['loca_status']?>
                          </td>
                      </tr>
    <?php
    
    
    
    $stmt  = $dbh->prepare($sql);
    $stmt->execute();
    $result = $stmt->get_result();
    $data = $result->fetch_all(mysqlI_ASSOC);
    
    $printed = array();
    
    // To get tree hierarchy usually means recursion of data.
    // Here we will try to use an associate array and set a
    // 'path' value to represent the hierarchy tree in one
    // pass. Sorting this array by the path value should give
    // a nice tree order and reference.
// The array key will be the unique id (loca_id) for each row.
// The value for each key will the complete row from the database.
// The row contains a element 'loca_path' - we will write the path
// for each row here. A child's path will be parent_path/child_name.
// For any child we encounter with a parent we look up the parents path
// using the loca_parent_id as the key.
// Caveat, although tested quickly, just make sure that all parents are
// returned first by the query.
    
    foreach ($data as $row)
    
    
       if ( $row['loca_parent_id'] == '' ) // Root Parent
       
          $row['loca_path'] = $row['loca_name'] . '/';
          $printed[$row['loca_id']] = $row;
       
       else // Child/Sub-Parent
       
          $row['loca_path'] = $printed[$row['loca_parent_id']]['loca_path'] . $row['loca_name'] . '/';
          $printed[$row['loca_id']] = $row;
       
    
    
    // Array with paths built, now sort then print
    
    array_multisort(array_column($printed, 'loca_path'), SORT_ASC, $printed);
    
    foreach ( $printed as $prow )
    
       print_row ( $prow );
    
    ?>
    </table>

【讨论】:

【参考方案11】:

如何创建动态树视图和菜单

步骤1:首先我们将在mysql数据库中创建treeview表。此表包含四列。id 是任务 id,name 是任务名称。

-
-- Table structure for table `treeview_items`
--

CREATE TABLE IF NOT EXISTS `treeview_items` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  `title` varchar(200) NOT NULL,
  `parent_id` varchar(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;

--
-- Dumping data for table `treeview_items`
--

INSERT INTO `treeview_items` (`id`, `name`, `title`, `parent_id`) VALUES
(1, 'task1', 'task1title', '2'),
(2, 'task2', 'task2title', '0'),
(3, 'task3', 'task1title3', '0'),
(4, 'task4', 'task2title4', '3'),
(5, 'task4', 'task1title4', '3'),
(6, 'task5', 'task2title5', '5');

第二步:树视图递归方法 我在树下创建了 createTreeView() 方法,如果当前任务 ID 大于上一个任务 ID,则该方法调用递归。

function createTreeView($array, $currentParent, $currLevel = 0, $prevLevel = -1) 

foreach ($array as $categoryId => $category) 

if ($currentParent == $category['parent_id'])                        
    if ($currLevel > $prevLevel) echo " <ol class='tree'> "; 

    if ($currLevel == $prevLevel) echo " </li> ";

    echo '<li> <label for="subfolder2">'.$category['name'].'</label> <input type="checkbox" name="subfolder2"/>';

    if ($currLevel > $prevLevel)  $prevLevel = $currLevel; 

    $currLevel++; 

    createTreeView ($array, $categoryId, $currLevel, $prevLevel);

    $currLevel--;               
       



if ($currLevel == $prevLevel) echo " </li>  </ol> ";


步骤 3:创建索引文件以显示树视图。 这是树视图示例的主文件,这里我们将调用带有所需参数的 createTreeView() 方法。

 <body>
<link rel="stylesheet" type="text/css" href="_styles.css" media="screen">
<?php
mysql_connect('localhost', 'root');
mysql_select_db('test');


$qry="SELECT * FROM treeview_items";
$result=mysql_query($qry);


$arrayCategories = array();

while($row = mysql_fetch_assoc($result)) 
 $arrayCategories[$row['id']] = array("parent_id" => $row['parent_id'], "name" =>                       
 $row['name']);   
  
?>
<div id="content" class="general-style1">
<?php
if(mysql_num_rows($result)!=0)

?>
<?php 

createTreeView($arrayCategories, 0); ?>
<?php

?>

</div>
</body>

第 4 步:创建 CSS 文件 style.css 在这里,我们将编写所有与 css 相关的类,目前我正在使用订单列表创建树视图。您也可以在此处更改图像路径。

img  border: none; 
input, select, textarea, th, td  font-size: 1em; 

/* CSS Tree menu styles */
ol.tree

    padding: 0 0 0 30px;
    width: 300px;

    li 
     
        position: relative; 
        margin-left: -15px;
        list-style: none;
    
    li.file
    
        margin-left: -1px !important;
    
        li.file a
        
            background: url(document.png) 0 0 no-repeat;
            color: #fff;
            padding-left: 21px;
            text-decoration: none;
            display: block;
        
        li.file a[href *= '.pdf']    background: url(document.png) 0 0 no-repeat; 
        li.file a[href *= '.html']   background: url(document.png) 0 0 no-repeat; 
        li.file a[href $= '.css']    background: url(document.png) 0 0 no-repeat; 
        li.file a[href $= '.js']         background: url(document.png) 0 0 no-repeat; 
    li input
    
        position: absolute;
        left: 0;
        margin-left: 0;
        opacity: 0;
        z-index: 2;
        cursor: pointer;
        height: 1em;
        width: 1em;
        top: 0;
    
        li input + ol
        
            background: url(toggle-small-expand.png) 40px 0 no-repeat;
            margin: -0.938em 0 0 -44px; /* 15px */
            height: 1em;
        
        li input + ol > li  display: none; margin-left: -14px !important; padding-left: 1px; 
    li label
    
        background: url(folder-horizontal.png) 15px 1px no-repeat;
        cursor: pointer;
        display: block;
        padding-left: 37px;
    

    li input:checked + ol
    
        background: url(toggle-small.png) 40px 5px no-repeat;
        margin: -1.25em 0 0 -44px; /* 20px */
        padding: 1.563em 0 0 80px;
        height: auto;
    
        li input:checked + ol > li  display: block; margin: 0 0 0.125em;  /* 2px */
        li input:checked + ol > li:last-child  margin: 0 0 0.063em; /* 1px */ 

More Details

【讨论】:

以上是关于将一系列父子关系转化为层次树?的主要内容,如果未能解决你的问题,请参考以下文章

Mysql 查询语句实现无限层次父子关系查询

Mysql 查询语句实现无限层次父子关系查询

Mysql 查询语句实现无限层次父子关系查询

Mysql 查询语句实现无限层次父子关系查询

第一章概论

二叉树和平衡二叉树的思考与实现