树操作算法,非递归方法

前些日子和一些朋友讨论关于无限分类的算法问题,其中提到了利用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]

posted @ 2017-07-04 17:06  星之蓝  阅读(220)  评论(0编辑  收藏  举报