首页 > PHP编程 > PHP无限级分类的实现(不使用递归)
2016
10-21

PHP无限级分类的实现(不使用递归)

 

  • 1.实现原理

  • 2.数据结构

  • 3.输出ul列表形式

  • 4.输出option列表形式

  • 5. 查找某一分类的所有子类

  • 6. 查找某一分类的所有父类

  • 7. 相关函数

  • 8.另一种算法

无限级分类在开发中经常使用,例如:部门结构、文章分类。无限级分类的难点在于“输出”和“查询”,例如

  • 将文章分类输出为<ul>列表形式;

  • 查找分类A下面所有分类包含的文章。

1.实现原理

第一种方案:

使用递归算法,也是使用频率最多的,大部分开源程序也是这么处理,不过一般都只用到四级分类。这种算法的数据库结构设计最为简单。category表中一个字段id,一个字段fid(父id)。这样可以根据WHERE id = fid来判断上一级内容,运用递归至最顶层。

分析:通过这种数据库设计出的无限级,可以说读取的时候相当费劲,所以大部分的程序最多3-4级分类,这就足以满足需求,从而一次性读出所有的数据,再对得到数组或者对象进行递归。本身负荷还是没太大问题。但是如果分类到更多级,那是不可取的办法。
这样看来这种分类有个好处,就是增删改的时候轻松了…然而就二级分类而言,采用这种算法就应该算最优先了。

 

第二种方案:

设置fid字段类型为varchar,将父类id都集中在这个字段里,用符号隔开,比如:1,3,6
这样可以比较容易得到各上级分类的ID,而且在查询分类下的信息的时候,
可以使用:SELECT * FROM category WHERE pid LIKE “1,3%”。


分析:相比于递归算法,在读取数据方面优势非常大,但是若查找该分类的所有 父分类 或者 子分类 查询的效率也不是很高,至少也要二次query,从某种意义看上,个人觉得不太符合数据库范式的设计。倘若递增到无限级,还需考虑字段是否达到要求,而且在修改分类和转移分类的时候操作将非常麻烦。

暂时,在自己项目中用的就是类似第二种方案的解决办法。就该方案在我的项目中存在这样的问题, 如果当所有数据记录达到上万甚至10W以上后,一次性将所以分类,有序分级的现实出来,效率很低。极有可能是项目处理数据代码效率低带来的。现在正在改良。

2.数据结构

idfidtitle
10中国
21江苏
31安徽
48江阴
53芜湖
63合肥
73蚌埠
82无锡
<?php
    $list = array(
        array('id'=>1, 'fid'=>0, 'title' => '中国'), 
        array('id'=>2, 'fid'=>1, 'title' => '江苏'),
        array('id'=>3, 'fid'=>1, 'title' => '安徽'),
        array('id'=>4, 'fid'=>8, 'title' => '江阴'),
        array('id'=>5, 'fid'=>3, 'title' => '芜湖'),
        array('id'=>6, 'fid'=>3, 'title' => '合肥'),
        array('id'=>7, 'fid'=>3, 'title' => '蚌埠'),
        array('id'=>8, 'fid'=>8, 'title' => '无锡')
    );
?>

各分类之间通过父类id(即fid)进行级别“串联”,形成一棵分类树。在进行串联时候有一点值得注意:分类A的fid不可以是其子类的id。

在使用这种数据结构进行输出时最常用的算法就是“递归”,熟悉PHP语言的朋友肯定知道,PHP不擅长递归 ,而且递归次数有限(100次左右,因操作系统和配置而异)。

由于所有的递归均可以使用循环实现,本文根据PHP语言特点编写了一套关于“无限级”分类的函数,相比递归实现而言效率更高

3.输出ul列表形式

将上述数据输出为下面的HTML

<ul>
    <li class="first-child">
        <div>江苏</div>
        <ul>
            <li class="first-child last-child">
                <div>无锡</div>
                <ul>
                    <li class="first-child last-child">
                        <div>江阴</div>
                    </li>
                </ul>
            </li>
        </ul>
    </li>
    <li class="last-child">
        <div>安徽</div>
        <ul>
            <li class="first-child"><div>芜湖</div></li>
            <li><div>合肥</div></li>
            <li class="last-child"><div>蚌埠</div></li>
        </ul>
    </li>
</ul>

这种HTML结构在前端使用(使用JavaScript和CSS构造可折叠树)十分方便。具体实现程序如下:

<ul><?php echo get_tree_ul($list, 1); ?></ul>

4.输出option列表形式

<select>
    <option value="2">江苏</option>
    <option value="8">&nbsp;&nbsp;&nbsp;&nbsp;无锡</option>
    <option value="4">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;江阴</option>
    <option value="3">安徽</option>
    <option value="5">&nbsp;&nbsp;&nbsp;&nbsp;芜湖</option>
    <option value="6">&nbsp;&nbsp;&nbsp;&nbsp;合肥</option>
    <option value="7">&nbsp;&nbsp;&nbsp;&nbsp;蚌埠</option>
</select>

具体实现程序如下:

<select>
<?php
    // get_tree_option()返回数组,并为每个元素增加了“深度”(即depth)列,直接输出即可
    $options = get_tree_option($list, 1); 
    foreach($options as $op) {
        echo '<option value="' . $op['id'] .'">' . str_repeat("&nbsp", $op['depth'] * 4) . $op['title'] . '</option>';
    }
?>
</select>

5. 查找某一分类的所有子类

<?php
    $children = get_tree_child($list, 0);
    echo implode(',', $children);    // 输出:1,3,2,7,6,5,8,4
?>

6. 查找某一分类的所有父类

<?php
    $children = get_tree_parent($list, 4);
    echo implode(',', $children);    //8, 2, 10
?>

7. 相关函数

<?php
function get_tree_child($data, $fid) {
    $result = array();
    $fids = array($fid);
    do {
        $cids = array();
        $flag = false;
        foreach($fids as $fid) {
            for($i = count($data) - 1; $i >=0 ; $i--) {
                $node = $data[$i];
                if($node['fid'] == $fid) {
                    array_splice($data, $i , 1);
                    $result[] = $node['id'];
                    $cids[] = $node['id'];
                    $flag = true;
                }
            }
        }
        $fids = $cids;
    } while($flag === true);
    return $result;
}

function get_tree_parent($data, $id) {
    $result = array();
    $obj = array();
    foreach($data as $node) {
        $obj[$node['id']] = $node;
    }    

    $value = isset($obj[$id]) ? $obj[$id] : null;    
    while($value) {
        $id = null;
        foreach($data as $node) {
            if($node['id'] == $value['fid']) {
                $id = $node['id'];
                $result[] = $node['id'];
                break;
            }
        }
        if($id === null) {
            $result[] = $value['fid'];
        }
        $value = isset($obj[$id]) ? $obj[$id] : null;
    }
    unset($obj);
    return $result;
}

function get_tree_ul($data, $fid) {
    $stack = array($fid);
    $child = array();
    $added_left = array();
    $added_right= array();
    $html_left     = array();
    $html_right    = array();
    $obj = array();
    $loop = 0;
    foreach($data as $node) {
        $pid = $node['fid'];
        if(!isset($child[$pid])) {
            $child[$pid] = array();
        }
        array_push($child[$pid], $node['id']);
        $obj[$node['id']] = $node;
    }

    while (count($stack) > 0) {    
        $id = $stack[0];
        $flag = false;
        $node = isset($obj[$id]) ? $obj[$id] : null;
        if (isset($child[$id])) {
            $cids = $child[$id];
            $length = count($cids);
            for($i = $length - 1; $i >= 0; $i--) {
                array_unshift($stack, $cids[$i]);
            }
            $obj[$cids[$length - 1]]['isLastChild'] = true;
            $obj[$cids[0]]['isFirstChild'] = true;
            $flag = true;
        }
        if ($id != $fid && $node && !isset($added_left[$id])) {
            if(isset($node['isFirstChild']) && isset($node['isLastChild']))  {
                $html_left[] = '<li class="first-child last-child">';
            } else if(isset($node['isFirstChild'])) {
                $html_left[] = '<li class="first-child">';
            } else if(isset($node['isLastChild'])) {
                $html_left[] = '<li class="last-child">';
            } else {
                $html_left[] = '<li>';
            }            
            $html_left[] = ($flag === true) ? "<div>{$node['title']}</div><ul>" : "<div>{$node['title']}</div>";
            $added_left[$id] = true;
        }    
        if ($id != $fid && $node && !isset($added_right[$id])) {
            $html_right[] = ($flag === true) ? '</ul></li>' : '</li>';
            $added_right[$id] = true;
        }

        if ($flag == false) {
            if($node) {
                $cids = $child[$node['fid']];
                for ($i = count($cids) - 1; $i >= 0; $i--) {
                    if ($cids[$i] == $id) {
                        array_splice($child[$node['fid']], $i, 1);
                        break;
                    }
                } 
                if(count($child[$node['fid']]) == 0) {
                    $child[$node['fid']] = null;
                }
            }
            array_push($html_left, array_pop($html_right));
            array_shift($stack);
        }
        $loop++;
        if($loop > 5000) return $html_left;
    }
    unset($child);
    unset($obj);
    return implode('', $html_left);
}

function get_tree_option($data, $fid) {
    $stack = array($fid);
    $child = array();
    $added = array();
    $options = array();
    $obj = array();
    $loop = 0;
    $depth = -1;
    foreach($data as $node) {
        $pid = $node['fid'];
        if(!isset($child[$pid])) {
            $child[$pid] = array();
        }
        array_push($child[$pid], $node['id']);
        $obj[$node['id']] = $node;
    }

    while (count($stack) > 0) {    
        $id = $stack[0];
        $flag = false;
        $node = isset($obj[$id]) ? $obj[$id] : null;
        if (isset($child[$id])) {
            for($i = count($child[$id]) - 1; $i >= 0; $i--) {
                array_unshift($stack, $child[$id][$i]);
            }
            $flag = true;
        }
        if ($id != $fid && $node && !isset($added[$id])) {
            $node['depth'] = $depth;
            $options[] = $node;
            $added[$id] = true;
        }
        if($flag == true){
            $depth++;
        } else {
            if($node) {
                for ($i = count($child[$node['fid']]) - 1; $i >= 0; $i--) {
                    if ($child[$node['fid']][$i] == $id) {
                        array_splice($child[$node['fid']], $i, 1);
                        break;
                    }
                } 
                if(count($child[$node['fid']]) == 0) {
                    $child[$node['fid']] = null;
                    $depth--;
                }
            }
            array_shift($stack);
        }
        $loop++;
        if($loop > 5000) return $options;
    }
    unset($child);
    unset($obj);
    return $options;
}
?>

8. 另一种算法:

--
-- Table structure for table `category`
--
CREATE TABLE IF NOT EXISTS `category` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `catpath` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=11 ;
--
-- Dumping data for table `category`
--
INSERT INTO `category` (`id`, `catpath`, `name`) VALUES
(1, '0', '网站首页'),
(2, '0-1', 'Linux OS'),
(3, '0-1', 'Apache服务器'),
(4, '0-1', 'MySQL数据库'),
(5, '0-1', 'PHP脚本语言'),
(6, '0-1-2', 'Linux 系统教程'),
(7, '0-1-2', 'Linux 网络技术'),
(8, '0-1-2', 'Linux 安全基础'),
(9, '0-1-2-7', 'Linux LAMP'),
(10, '0-1-3-10', 'apache Server');

这里说明下,catpath的-链接符号不是固定的,可以选择,;等特殊符号。

$conn = mysql_connect ( 'localhost', 'root', '' );
mysql_select_db ( 'test', $conn );
mysql_query ( 'set names UTF8' );
$sql = "select id,concat(catpath,'-',id) as abspath,name from category order by abspath";
$query = mysql_query ( $sql );
while ( $row = mysql_fetch_array ( $query ) ) {
    /**
     * 第一种展示方法
     */
    /*$space = str_repeat ( '&nbsp;&nbsp;&nbsp;&nbsp;', count ( explode ( '-', $row ['abspath'] ) ) - 1 );
    echo $space . $row ['name'] . '<br>';*/
    /**
    * 第二种展示方法
     */
    $space = str_repeat ( '&nbsp;&nbsp;&nbsp;&nbsp;', count ( explode ( '-', $row ['abspath'] ) ) - 1 );
    $option .= '<option value="' . $row ['id'] . '">' . $space . $row ['name'] . '</option>';
}
echo '<select name="opt">' . $option . '</select>';

 

 

作者:admin
admin
TTF的家园-www.ttfde.top 个人博客以便写写东西,欢迎喜欢互联网的朋友一起交流!

本文》有 0 条评论

留下一个回复