递归一:
1 function tree($table,$p_id=0) {
2 $tree = array();
3 foreach($table as $row){
4 if($row['parent_id']==$p_id){
5 $tmp = tree($table,$row['id']);
6 if($tmp){
7 $row['children']=$tmp;
8 }else{
9 $row['leaf'] = true;
10 }
11 $tree[]=$row;
12 }
13 }
14 return $tree;
15 }
该递归算法转自: http://bbs.phpchina.com/thread-81976-1-1.html
非递归:
1 /**
2 * 创建父节点树形数组
3 * 参数
4 * $ar 数组,邻接列表方式组织的数据
5 * $id 数组中作为主键的下标或关联键名
6 * $pid 数组中作为父键的下标或关联键名
7 * 返回 多维数组
8 **/
9 function find_parent($ar, $id='id', $pid='pid') {
10 foreach($ar as $v) $t[$v[$id]] = $v;
11 foreach ($t as $k => $item){
12 if( $item[$pid] ){
13 if( ! isset($t[$item[$pid]]['parent'][$item[$pid]]) )
14 $t[$item[$id]]['parent'][$item[$pid]] =& $t[$item[$pid]];
15 }
16 }
17 return $t;
18 }
19
20
21 /**
22 * 创建子节点树形数组
23 * 参数
24 * $ar 数组,邻接列表方式组织的数据
25 * $id 数组中作为主键的下标或关联键名
26 * $pid 数组中作为父键的下标或关联键名
27 * 返回 多维数组
28 **/
29 function find_child($ar, $id='id', $pid='pid') {
30 foreach($ar as $v) $t[$v[$id]] = $v;
31 foreach ($t as $k => $item){
32 if( $item[$pid] ) {
33 $t[$item[$pid]]['child'][$item[$id]] =& $t[$k];
34 }
35 }
36 return $t;
37 }
实例:
$data = array(
array('ID'=>1, 'PARENT'=>0, 'NAME'=>'祖父'),
array('ID'=>2, 'PARENT'=>1, 'NAME'=>'父亲'),
array('ID'=>3, 'PARENT'=>1, 'NAME'=>'叔伯'),
array('ID'=>4, 'PARENT'=>2, 'NAME'=>'自己'),
array('ID'=>5, 'PARENT'=>4, 'NAME'=>'儿子'),
);
$p = find_parent($data, 'ID', 'PARENT');
$c = find_child($data, 'ID', 'PARENT');
该非递归算法转自:http://topic.csdn.net/u/20110728/15/eadffb68-5eb6-40d8-9ec1-2bc439f45322.html
非递归另一算法:
前些日子和一些朋友讨论关于无限分类的算法问题,其中提到了利用PHP数组进行操作的想法。其实无限分类算法就是树的算法。
利用数据库存储数据加递归搜索似乎是目前很流行的一种方法,但这种方法的效率低下,并增加了不少数据库的负担。
我这个算法采用文本方式存储数据,效率应该还可以。由于时间关系,算法是匆匆完成没有经过严格测试就放上来了,大家有兴趣的话就测试一下,找找其中的bug,或改善一下算法的性能。
提醒一点,如果你的PHP可利用的内存不到1G,请不要尝试去创建一个具有100万个节点的树。。。:lol:
树的操作算法是各种高级数据结构算法的基础,估计不少同学在大学的时候都学烂了,所以我就不必细讲了。现在要讨论的是如何在PHP这个编程环境里面如果实现该算法最大的性能。
好吧,我来抛砖引玉。
因时间关系没有写注释,看不懂的同学可以提问。

/**
* 树操作类
*
* 利用php数组对树进行构造,删除和遍历等操作,全部遍历采用非递归方法实现,并实现Iterator和Countable操作。
* 该算法可适用与无限分类等相关领域,对于同时操作百万级以上的数据可能需要512M以上的内存支持。
* 利用文本存储数据结构,如果需要数据库存储可继承本类并覆盖loadTree和saveTree方法。
*/
class Itc_Tree implements Iterator,Countable
{
const ID = 'i';
const PID = 'p';
const LEVEL ='l';
const DATA = 'd';
const CHILDREN = 'c';
const DFS = 'DFS';
const BFS = 'BFS';
protected $_tree = array();
protected $_filename = '';
protected $_prefix = 'tree_';
protected $_path = '';
protected $_rootid = 0;
protected $_lastid = 0;
protected $_way = self::DFS;
protected $_stack = array();
public function __construct($name, $path = '')
{
$this->loadTree($name, $path);
}
public function loadTree ($name, $path = '')
{
if ($path != '') {
if (is_dir($path)) {
$this->_path = rtrim($path, '/\\') . DIRECTORY_SEPARATOR;
} else {
die('No such directory :' . $path);
}
}
$name = md5($name);
$this->_filename = $this->_path . $this->_prefix . $name ;
if (is_file($this->_filename)) {
$this->_tree = json_decode(file_get_contents($this->_filename),true);
$this->_initTree();
return true;
}
return false;
}
protected function _initTree ()
{
$ary = $this->listToTree($this->_tree);
$this->_rootid = $ary['topid'];
$this->_lastid = $ary['lastid'];
$this->_tree[$this->_rootid][self::LEVEL] = 1;
return $this;
}
public function listToTree(&$list){
$return = array('topid'=>0,'lastid'=>0);
foreach ($list as $id => &$node){
if (isset($list[$node[self::PID]]))
$list[$node[self::PID]][self::CHILDREN][$id] = &$node;
else
$return['topid'] = $id;
if ($id > $return['lastid']) $return['lastid'] = $id;
}
return $return;
}
public function setTreeByList (array $list)
{
if (!$this->_checkList($list)){
die('Incorrect data format');
}
$this->_tree = $list;
$this->_initTree();
return true;
}
public function getTree($id = null)
{
if (null === $id) $id = $this->_rootid;
if (isset($this->_tree[$id]))
return $this->_tree[$id];
else
return null;
}
public function getParent($id){
if (isset($this->_tree[$id]) && isset($this->_tree[$this->_tree[$id][self::PARENT]])){
return $this->_tree[$this->_tree[$id][self::PARENT]];
}
return null;
}
public function getParentsId($start,$level = null)
{
$parent = array();
$id = $start;
do{
if (!isset($this->_tree[$id])) break;
$pid = $this->_tree[$id][self::PID];
if (null !== $level && !$level--) break;
if (!isset($this->_tree[$pid])) break;
}
while(true);
return $parent;
}
public function getChildren($id){
if (isset($this->_tree[$id])){
return $this->_tree[$id][self::CHILDREN];
}
return null;
}
public function getTreeAll()
{
return $this->_tree;
}
public function getTreeList()
{
if (isset($this->_tree)) {
return $this->toList($this->_tree);
}
return null;
}
public function toList($tree){
$ary = array();
foreach ($tree as $id => $node){
unset($node[self::CHILDREN]);
$ary[$id] = $node;
}
return $ary;
}
public function getDepth(){
$depth = 1;
$id = $this->_rootid;
$stack = array($id);
$i = 0;
if (!isset($this->_tree[$id][self::LEVEL]))
$this->_tree[$id][self::LEVEL] = 1;
do{
$id = $stack[$i++];
$pid = $this->_tree[$id][self::PID];
if (isset($this->_tree[$id][self::CHILDREN])){
$stack = array_merge($stack,array_keys($this->_tree[$id][self::CHILDREN]));
}
if (isset($this->_tree[$id][self::LEVEL])){
$level = $this->_tree[$id][self::LEVEL];
}
else {
$level = $this->_tree[$pid][self::LEVEL] + 1;
$this->_tree[$id][self::LEVEL] = $level;
}
if ($depth < $level) $depth = $level;
}while(isset($stack[$i]));
return $depth;
}
public function getLastId(){
return $this->_lastid;
}
public function getRootId(){
return $this->_rootid;
}
public function getRoot(){
return $this->_tree[$this->_rootid];
}
public function getData($id){
if (isset($this->_tree[$id])){
return $this->_tree[$id][self::DATA];
}
return null;
}
public function setData($id,$data){
if (isset($this->_tree[$id])){
$this->_tree[$id][self::DATA] = $data;
return true;
}
return false;
}
public function addRoot($data = null)
{
$root = array(
self::ID => $this->_rootid,
self::PID => $this->_rootid - 1,
self::LEVEL => 1,
self::DATA => $data
);
$this->_tree[$this->_rootid] = &$root;
$this->_lastid = $this->_rootid;
return $this->_rootid;
}
public function addLeaf($pid,$data = null)
{
if (!isset($this->_tree[$pid])){
if (isset($this->_tree[$this->_rootid]))
$pid = $this->_rootid;
else {
$this->_lastid = $this->addRoot($data);
return $this->_lastid;
}
}
$id = ++$this->_lastid;
$level = isset($this->_tree[$pid][self::LEVEL])? $this->_tree[$pid][self::LEVEL] + 1 : null;
$this->_tree[$id] = array(
self::ID => $id,
self::PID => $pid,
self::LEVEL => $level,
self::DATA => $data
);
$this->_tree[$pid][self::CHILDREN][$id] = &$this->_tree[$id];
return $id;
}
public function insertChild($pid, $children)
{
if (!isset($this->_tree[$pid])){
return false;
}
if ($children instanceof self){
$children = $children->getTree();
}
if (is_array($children)){
$children = $this->toList($children);
$lastid = $this->_lastid + 1;
$list = array();
foreach ($children as $id => $node){
$node[self::ID] +=$lastid;
$node[self::PID] +=$lastid;
$list[$id + $lastid] = $node;
}
$ary = $this->listToTree($list);
$this->_lastid = $ary['lastid'];
$list[$ary['topid']][self::PID] = $pid;
$this->_tree += $list;
$this->_tree[$pid][self::CHILDREN][$ary['topid']] = &$this->_tree[$ary['topid']];
return $this->_lastid;
}
return $this->addLeaf($pid,$children);
}
public function deleteChild($id){
if (!isset($this->_tree[$id])){
return null;
}
$pid = $this->_tree[$id][self::PID];
$delTree = array();
if (isset($this->_tree[$pid])){
unset($this->_tree[$pid][self::CHILDREN][$id]);
}
$stack = array($id);
do{
$id = array_pop($stack);
if (isset($this->_tree[$id][self::CHILDREN]))
$stack =array_merge($stack, array_keys($this->_tree[$id][self::CHILDREN]));
$delTree[$id] = $this->_tree[$id];
unset($this->_tree[$id]);
}while(!empty($stack));
return $delTree;
}
public function emptyTree(){
$this->_tree = array();
$this->_rootid = 0;
$this->_lastid = 0;
return true;
}
public function destroyTree(){
$this->emptyTree();
if (is_file($this->_filename))
unlink($this->_filename);
return true;
}
protected function _checkList (array $list)
{
$isChecked = true;
$topNum = 0;
foreach ($list as $id => $node) {
if (! isset($node[self::ID]) || ! isset($node[self::PID]) || $node[self::ID] != $id) {
$isChecked = false;
break;
}else{
if (!isset($list[$node[self::PID]])) $topNum++;
if ($topNum > 1){
$isChecked = false;
break;
}
}
}
return $isChecked;
}
public function getLevel($id){
if (isset($this->_tree[$id]) && isset($this->_tree[$id][self::LEVEL]) )
return $this->_tree[$id][self::LEVEL];
$level = 0;
$id2 = $id;
while(isset($this->_tree[$id2])){
if (isset($this->_tree[$id2][self::LEVEL])){
$level += $this->_tree[$id2][self::LEVEL];
break;
}
$level++;
$id2 = $this->_tree[$id2][self::PID];
}
if (isset($this->_tree[$id]))
$this->_tree[$id][self::LEVEL] = $level;
return $level;
}
public function DFS($callback = null,$id = null){
if (null === $id) $id = $this->_rootid;
if (!isset($this->_tree[$id])){
return null;
}
if (null == $callback || !function_exists($callback)){
return null;
}
$stack = array($id);
$return = null;
do{
$id = array_shift($stack);
$children = null;
if (isset($this->_tree[$id][self::CHILDREN])){
$children = array_keys($this->_tree[$id][self::CHILDREN]);
$stack = array_merge($children,$stack);
}
$args = array(
self::ID => $id,
self::PID => $this->_tree[$id][self::PID],
self::DATA => &$this->_tree[$id][self::DATA],
self::LEVEL => $this->getLevel($id),
self::CHILDREN => $children
);
$back = call_user_func($callback,$args);
if (isset($back)){
if (is_string($back)) $return .= $back;
else if (is_array($back)) $return[] = $back;
else if (is_int($back)) $return += $back;
else if (is_bool($back) && $back) $return = $this->_tree[$id];
}
}while(!empty($stack));
return $return;
}
public function BFS($callback = null,$id = null){
if (null === $id) $id = $this->_rootid;
if (!isset($this->_tree[$id])){
return array();
}
if (null == $callback || !function_exists($callback)){
$callback = null;
}
$stack = array($id);
$return = null;
do{
$id = array_shift($stack);
$children = null;
if (isset($this->_tree[$id][self::CHILDREN])){
$children = array_keys($this->_tree[$id][self::CHILDREN]);
$stack = array_merge($stack,$children);
}
$args = array(
self::ID => $id,
self::PID => $this->_tree[$id][self::PID],
self::DATA => &$this->_tree[$id][self::DATA],
self::LEVEL => $this->getLevel($id),
self::CHILDREN => $children
);
$return = call_user_func($callback,$args);
if (isset($back)){
if (is_string($back)) $return .= $back;
else if (is_array($back)) $return[] = $back;
else if (is_int($back)) $return += $back;
else if (is_bool($back) && $back) $return = $this->_tree[$id];
}
}while(!empty($stack));
return $return;
}
public function saveTree(){
file_put_contents($this->_filename, json_encode($this->toList($this->_tree)));
return true;
}
public function setWay($way = self::DFS){
if (in_array($way,array(self::DFS,self::BFS))){
$this->_way = $way;
$this->rewind();
}
return $this;
}
public function rewind()
{
if (isset($this->_tree[$this->_rootid]))
{
$this->_stack = array($this->_rootid);
}
else{
$this->_stack = array();
}
return $this;
}
public function next()
{
$id = array_shift($this->_stack);
if (isset($this->_tree[$id][self::CHILDREN])){
$children = array_keys($this->_tree[$id][self::CHILDREN]);
if ($this->_way == self::BFS)
$this->_stack = array_merge($this->_stack,$children);
else
$this->_stack = array_merge($children,$this->_stack);
}
}
public function count()
{
return count($this->_tree);
}
public function key()
{
return current($this->_stack);
}
public function current()
{
$id = current($this->_stack);
if (isset($this->_tree[$id])){
return $this->_tree[$id];
}
else
return null;
}
public function valid()
{
return !empty($this->_stack);
}
}
DEMO如下:

include '../lib/Itc/Tree.php';
$loop = 10;
$tree = new Itc_Tree('t10');
//获得最后一个ID
$id = $tree->getLastId();
$i=0;
//构造一棵10个结点的随机树
while($loop--){
$pid = rand(0,$id);
$id = $tree->addLeaf($pid,'data' . $i++);
}
//定义一个遍历访问函数
function visit($node){
printf("%'-" . $node[Itc_Tree::LEVEL] . "s%s[%d]\n",'',$node[Itc_Tree::DATA],$node[Itc_Tree::ID]);
}
//打印树结构
//print_r($tree->getTreeList());
echo "<pre>";
//广度优先遍历树1
echo "广度优先1:\n";
$tree->BFS('visit');
//广度优先遍历树2
echo "广度优先2:\n";
$tree->setWay(Itc_Tree::BFS);
foreach($tree as $id => $node){
echo $id,',';
}
echo "\n";
//深度优先遍历树1
echo "深度优先1:\n";
$tree->DFS('visit');
//深度优先遍历树
echo "深度优先2:\n";
$tree->setWay(Itc_Tree::DFS);
foreach($tree as $id => $node){
echo $id,',';
}
echo "\n";
echo "树的深度:";
echo $tree->getDepth() ,"\n";
echo "树的结构:";
print_r($tree->getTree());
echo "</pre>";
//保存树
$tree->saveTree();
///--------输出结果-------------------------
广度优先1:
-data0[0]
--data1[1]
--data3[3]
--data4[4]
--data6[6]
---data2[2]
---data7[7]
---data5[5]
---data8[8]
----data9[9]
广度优先2:
0,1,3,4,6,2,7,5,8,9,
深度优先1:
-data0[0]
--data1[1]
---data2[2]
----data9[9]
---data7[7]
--data3[3]
---data5[5]
---data8[8]
--data4[4]
--data6[6]
深度优先2:
0,1,2,9,7,3,5,8,4,6,
树的深度:4
树的结构:Array
(
=> 0
[p] => -1
[l] => 1
[d] => data0
[c] => Array
(
[1] => Array
(
=> 1
[p] => 0
[l] => 2
[d] => data1
[c] => Array
(
[2] => Array
(
=> 2
[p] => 1
[l] => 3
[d] => data2
[c] => Array
(
[9] => Array
(
=> 9
[p] => 2
[l] => 4
[d] => data9
)
)
)
[7] => Array
(
=> 7
[p] => 1
[l] => 3
[d] => data7
)
)
)
[3] => Array
(
=> 3
[p] => 0
[l] => 2
[d] => data3
[c] => Array
(
[5] => Array
(
=> 5
[p] => 3
[l] => 3
[d] => data5
)
[8] => Array
(
=> 8
[p] => 3
[l] => 3
[d] => data8
)
)
)
[4] => Array
(
=> 4
[p] => 0
[l] => 2
[d] => data4
)
[6] => Array
(
=> 6
[p] => 0
[l] => 2
[d] => data6
)
)
)
该算法转自:http://bbs.phpchina.com/thread-81976-1-1.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述