树操作算法,非递归方法
前些日子和一些朋友讨论关于无限分类的算法问题,其中提到了利用PHP数组进行操作的想法。其实无限分类算法就是树的算法。利用数据库存储数据加递归搜索似乎是目前很流行的一种方法,但这种方法的效率低下,并增加了不少数据库的负担。
我这个算法采用文本方式存储数据,效率应该还可以。由于时间关系,算法是匆匆完成没有经过严格测试就放上来了,大家有兴趣的话就测试一下,找找其中的bug,或改善一下算法的性能。
提醒一点,如果你的PHP可利用的内存不到1G,请不要尝试去创建一个具有100万个节点的树。。。:lol:
树的操作算法是各种高级数据结构算法的基础,估计不少同学在大学的时候都学烂了,所以我就不必细讲了。现在要讨论的是如何在PHP这个编程环境里面如果实现该算法最大的性能。
好吧,我来抛砖引玉。
因时间关系没有写注释,看不懂的同学可以提问。
[php]
<?php
/**
* 树操作类
*
* 利用php数组对树进行构造,删除和遍历等操作,全部遍历采用非递归方法实现,并实现Iterator和Countable操作。
* 该算法可适用与无限分类等相关领域,对于同时操作百万级以上的数据可能需要512M以上的内存支持。
* 利用文本存储数据结构,如果需要数据库存储可继承本类并覆盖loadTree和saveTree方法。
*
* @copyrihgt [email=sentrychen@hotmail.com]sentrychen@hotmail.com[/email], 2008
* @author sentrychen [email=sentrychen@hotmail.com]sentrychen@hotmail.com[/email]>
* @version $Id: Tree.php, v 0.0.1 2008/10/01 11:51:25 uw Exp $
*/
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);
}
}
[/php]
[[i] 本帖最后由 sentrychen 于 2008-10-3 11:08 编辑 [/i]] 功能测试
[php]
<?php
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
(
[i] => 0
[p] => -1
[l] => 1
[d] => data0
[c] => Array
(
[1] => Array
(
[i] => 1
[p] => 0
[l] => 2
[d] => data1
[c] => Array
(
[2] => Array
(
[i] => 2
[p] => 1
[l] => 3
[d] => data2
[c] => Array
(
[9] => Array
(
[i] => 9
[p] => 2
[l] => 4
[d] => data9
)
)
)
[7] => Array
(
[i] => 7
[p] => 1
[l] => 3
[d] => data7
)
)
)
[3] => Array
(
[i] => 3
[p] => 0
[l] => 2
[d] => data3
[c] => Array
(
[5] => Array
(
[i] => 5
[p] => 3
[l] => 3
[d] => data5
)
[8] => Array
(
[i] => 8
[p] => 3
[l] => 3
[d] => data8
)
)
)
[4] => Array
(
[i] => 4
[p] => 0
[l] => 2
[d] => data4
)
[6] => Array
(
[i] => 6
[p] => 0
[l] => 2
[d] => data6
)
)
)
[/php] 删除子数和插入子树操作。
[php]
//利用原来保存的数据进行操作
include '../lib/Itc/Tree.php';
$tree = new Itc_Tree('t10');
echo "<pre>";
//删除结点为2的子树,返回被删除的子树
$delTree = $tree->deleteChild(2);
//在结点3插入刚刚被删除的子树
$tree->insertChild(3,$delTree);
print_r($tree->getTree());
echo "</pre>";
//输出结果
Array
(
[i] => 0
[p] => -1
[l] => 1
[d] => data0
[c] => Array
(
[1] => Array
(
[i] => 1
[p] => 0
[l] => 2
[d] => data1
[c] => Array
(
[7] => Array
(
[i] => 7
[p] => 1
[l] => 3
[d] => data7
)
)
)
[3] => Array
(
[i] => 3
[p] => 0
[l] => 2
[d] => data3
[c] => Array
(
[5] => Array
(
[i] => 5
[p] => 3
[l] => 3
[d] => data5
)
[8] => Array
(
[i] => 8
[p] => 3
[l] => 3
[d] => data8
)
[12] => Array
(
[i] => 12
[p] => 3
[l] => 3
[d] => data2
[c] => Array
(
[i] [19] => Array
(
[i] => 19
[p] => 12
[l] => 4
[d] => data9
)
)
)
)
)
[4] => Array
(
[i] => 4
[p] => 0
[l] => 2
[d] => data4
)
[6] => Array
(
[i] => 6
[p] => 0
[l] => 2
[d] => data6
)
)
)
[/php][/i][/i][/i][/i][/i][/i][/i][/i][/i][/i][/i]
[[i] 本帖最后由 sentrychen 于 2008-10-1 17:47 编辑 [/i]] //性能测试,感觉还不是很满意。构造一棵10万个结点的树,然后遍历,保存
[php]
include '../lib/Itc/Tree.php';
$loop = 100000;
test('');
$tree = new Itc_Tree('t100000');
//获得最后一个ID
$id = $tree->getLastId();
$i=0;
//构造一棵10万个结点的随机树
while($loop--){
$pid = rand(0,$id);
$id = $tree->addLeaf($pid,'data' . $i++);
}
test('创建时间');
foreach($tree as $id => $node){
$a = $node[Itc_Tree::PID];
}
test('遍历时间');
$tree->saveTree();
test('保存时间');
function test($tag){
static $pTime = 0;
static $pMemory = 0;
$nowTime = microtime(TRUE);
$nowMem = memory_get_usage();
if ($pTime){
echo $tag , " | " , number_format(($nowTime - $pTime),6)
," 秒| " , number_format($nowMem - $pMemory) , "字节<br>\n";
}
$pTime = $nowTime;
$pMemory = $nowMem;
}
//输出结果
创建时间 | 1.320593 秒| 60,750,304字节
遍历时间 | 2.449615 秒| 24,736字节
保存时间 | 0.704968 秒| 39,240字节
[/php] 呵呵,没人感兴趣,也许是太基础了或者我写得太烂 LZ高手 能打包成附件下载吗 先站位,慢慢研究!! 值得好好看看,
但实际运用,
犹豫了,
怕怕tukiz13 [quote]原帖由 [i]lxylxy888666[/i] 于 2008-10-5 01:26 发表 [url=http://bbs.phpchina.com/redirect.php?goto=findpost&pid=629756&ptid=81976][img]http://bbs.phpchina.com/images/common/back.gif[/img][/url]
值得好好看看,
但实际运用,
犹豫了,
怕怕tukiz13 [/quote]
呵呵,怕啥呢? 不知道你花了多少时间,,,
如果用数据库,我只要一两个小时,
而这样写。我一两天就写不出来。。
tukiz14 [quote]原帖由 [i]lxylxy888666[/i] 于 2008-10-5 10:07 发表 [url=http://bbs.phpchina.com/redirect.php?goto=findpost&pid=629851&ptid=81976][img]http://bbs.phpchina.com/images/common/back.gif[/img][/url]
不知道你花了多少时间,,,
如果用数据库,我只要一两个小时,
而这样写。我一两天就写不出来。。
tukiz14 [/quote]
花了一个上午的时间写,再花一个下午的时间调试。 我的哥啊,强啊 算法精妙,可以应用在很多实际场合,受益良多。
非常感谢楼主分享。
建议加精,以资鼓励。 五五说好,就是好! [quote]原帖由 [i]七月十五[/i] 于 2008-10-5 15:28 发表 [url=http://bbs.phpchina.com/redirect.php?goto=findpost&pid=630105&ptid=81976][img]http://bbs.phpchina.com/images/common/back.gif[/img][/url]
算法精妙,可以应用在很多实际场合,受益良多。
非常感谢楼主分享。
建议加精,以资鼓励。 [/quote]
呵呵,谢谢加精,这个算法我也没有在实际场合应用过,可能还有很多地方会有bug。
有空我再写一个二叉树的算法,二叉树有4种遍历方式,估计也有很多地方需要用到的。 这么长,哪看得懂啊。
把你的思路写出来就可以了。 学习,mark 我狂顶!!!!!
tukiz19 老话题 到底是把逻辑操作交给数据库 还是交给自己的代码!
回复 19# ws00377531 的帖子
具体问题具体分析如果是数据库负荷大的系统,尽量减轻数据库负担,减少数据库中的运算
页:
[1]
2
