前些日子和一些朋友讨论关于无限分类的算法问题,其中提到了利用php数组进行操作的想法。其实无限分类算法就是树的算法。 利用数据库存储数据加递归搜索似乎是目前很流行的一种方法,但这种方法的效率低下,并增加了不少数据库的负担。 我这个算法采用文本方式存储数据,效率应该还可以。由于时间关系,算法是匆匆完成没有经过严格测试就放上来了,大家有兴趣的话就测试一下,找找其中的bug,或改善一下算法的性能。 提醒一点,如果你的php可利用的内存不到1g,请不要尝试去创建一个具有100万个节点的树。。。:lol: 树的操作算法是各种高级数据结构算法的基础,估计不少同学在大学的时候都学烂了,所以我就不必细讲了。现在要讨论的是如何在php这个编程环境里面如果实现该算法最大的性能。 好吧,我来抛砖引玉。 因时间关系没有写注释,看不懂的同学可以提问。 [php] <?php /** * 树操作类 * * 利用php数组对树进行构造,删除和遍历等操作,全部遍历采用非递归方法实现,并实现iterator和countable操作。 * 该算法可适用与无限分类等相关领域,对于同时操作百万级以上的数据可能需要512m以上的内存支持。 * 利用文本存储数据结构,如果需要数据库存储可继承本类并覆盖loadtree和savetree方法。 * * @copyrihgt sentrychen@hotmail.com, 2008 * @author sentrychen sentrychen@hotmail.com> * @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]
|