改进型先根遍历算法
来源:互联网 发布:康佳网络电视怎么插u盘 编辑:程序博客网 时间:2024/06/07 17:53
<?php/** * 无效的分类Id * */class NodeNotFoundException extends Exception {public $id;function __construct($id){$this->id = $id;parent::__construct("指定 [ID = {$id}] 的节点不存在");}}/** * 节点名称已经存在 */class NodeNameExistsException extends Exception{public $name,$parentId;function __construct($name ,$parentId) {$this->name = $name;$this->parentId = $parentId; parent::__construct("指定 [NAME = {$name} ,PARENT_ID = {$parentId}] 的分类已经存在"); } }/** * 节点分类 模型 用“改进型先根遍历算法”在数据库中存储层次化的数据(通常所说的无限分类) * * 由于“改进型先根遍历算法”要求所有分类都是唯一一个根分类的子分类。 * 所以 NodeModel 假定一个名为“_#_ROOT_NODE_#_”的分类为唯一的根分类。 * * 应用程序在调用 NodeModel::create() 创建第一个分类时,会自动 * 判断根分类是否存在,并创建根分类。 * * 对于应用程序来说,“_#_ROOT_NODE_#_”分类是不存在的。所以,应用程序 * 可以创建多个父分类 ID 为 0 的“顶级分类”。这些顶级分类实际上就是 * “_#_ROOT_NODE_#_”分类的直接子分类。 * * 添加 deep 这个属性用于设置分类的深度,用于按名称查找分类,比如: * '/图书/IT/技术/PHP' 其 deep 就 为 * * 注意: 分类名称 中不能带有 "/" 符号,不然会出现意向不到的问题 * <sql> CREATE TABLE `nodes` ( `node_id` int(11) NOT NULL auto_increment, `parent_id` int(11) NOT NULL, `name` varchar(64) collate utf8_unicode_ci NOT NULL, `left_value` int(11) NOT NULL, `right_value` int(11) NOT NULL, `deep` int(4) NOT NULL, `created_at` datetime default NULL, `updated_at` datetime default NULL, PRIMARY KEY (`node_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; * </sql> * */class NodeModel {/** * @var CoreDb */protected $_dbo = NULL; /** * 数据表名称,在继承类中可能会被覆盖 * * @var string */ public $tableName = 'nodes'; /** * 主键字段名,在继承类中可能会被覆盖 * * @var string */ public $primaryKey = 'node_id'; /** * 根节点名 * * @var string */ protected $_rootNodeName = '_#_ROOT_NODE_#_'; /** * 直系子节点名称是否唯一 * * @var boolean */ protected $_uniqueNameSupport = true;function __construct(CoreDb $dbo){$this->_dbo = $dbo;$this->init();}protected function init(){$this->tableName = $this->_dbo->getDbUtils()->getTableName($this->tableName);}function find($cond, $fields='*'){$sqlCond = CoreDbSqlHelper::parseConditions($this->_dbo,$cond);if ($sqlCond) $sqlCond = "WHERE {$sqlCond}";$qfields = CoreDbSqlHelper::qfields($fields,$this->tableName);$sql = "SELECT {$qfields} FROM {$this->tableName} {$sqlCond}" . CoreDbSqlHelper::getLimitSql(1);return $this->_dbo->getRow($sql);}function findByPkv($nodeId, $fields='*') { return $this->find(array($this->primaryKey=>$nodeId),$fields); } /** * 添加用户 并返回插入的主键编号 * * @var array $user * * @return int */protected function insert(array $node){if (!isset($node['name'])) return false;$user['created_at'] = CURRENT_TIMESTAMP;$sql = CoreDbSqlHelper::getInsertSQL($node,$this->tableName);if ($sql){if ($this->_dbo->execute($sql,$node,true))return $this->_dbo->lastInsertId();}return false;} /** * 添加一个节点,返回该节点的 ID * * @param array $node * @param int $parentId * * @return int */ function create(array $node, $parentId=0) { $parentId = (int)$parentId; if ($parentId) { $parent = $this->find(array($this->primaryKey=>$parentId)); if (!$parent) { // 指定的父节点不存在 throw new NodeNotFoundException($parentId); } } else { // 如果 $parentId 为 0 或 null,则创建一个顶级节点 $parent = $this->find(array('name' => $this->_rootNodeName)); if (!$parent) { // 如果根节点不存在,则自动创建 $parent = array( 'name' => $this->_rootNodeName, 'left_value' => 1, 'right_value' => 2, 'parent_id' => -1, 'deep' => 0, ); if (!$this->insert($parent)) { return false; } } // 确保所有 _#_ROOT_NODE_#_ 的直接字节点的 parent_id 都为 0 $parent[$this->primaryKey] = 0; } //检验直系子分类名称是否唯一if ($this->_uniqueNameSupport && $this->nameExists($node['name'],$parent[$this->primaryKey])){throw new NodeNameExistsException($node['name'],$parent[$this->primaryKey]);} // 加上事务功能 $this->_dbo->startTrans(); // 根据父节点的左值和右值更新数据 $sql = "UPDATE {$this->tableName} SET left_value = left_value + 2 " . "WHERE left_value >= {$parent['right_value']}"; $this->_dbo->execute($sql); $sql = "UPDATE {$this->tableName} SET right_value = right_value + 2 " . "WHERE right_value >= {$parent['right_value']}"; $this->_dbo->execute($sql); // 插入新节点记录 $node['parent_id'] = $parent[$this->primaryKey]; $node['deep'] = $parent['deep'] + 1; //路径深度定义 $node['left_value'] = $parent['right_value']; $node['right_value'] = $parent['right_value'] + 1; $id = $this->insert($node); $this->_dbo->completeTrans(); return $id; }/** * 检验直系子分类名称是否唯一 * * @param string $name * @param int $parentId * @param int $exceptId 如果指定此参数,则排除掉对指定Id的校验 * * @return boolean */function nameExists($name,$parentId,$exceptId=null){$cond = array('parent_id' =>$parentId,'name'=>$name);if ($exceptId){$cond['id'] = array($exceptId,'!=');}return $this->_dbo->getDbUtils()->findCount($this->tableName,$cond);} function update(array $node){// 未指定主键if (!isset($node[$this->primaryKey])) return false;$node['updated_at'] = CURRENT_TIMESTAMP;$sql = CoreDbSqlHelper::getUpdateSQL($this->_dbo,$node,$this->primaryKey,$this->tableName);if ($sql){if ($this->_dbo->execute($sql,$node))return $this->_dbo->lastQueryAffectedRows();}return false;} /** * 更新节点信息 * * @param array $node * * @return boolean */ function save(array $node) { //检验节点名称是否在父节点下是唯一的 if (isset($node['name'])) { if ($this->_uniqueNameSupport && $this->nameExists($node['name'],$node['parent_id'],$node[$this->primaryKey])){throw new NodeNameExistsException($node['name'],$node[$this->primaryKey]);} } unset($node['left_value']); unset($node['right_value']); unset($node['parent_id']); unset($node['deep']); return $this->update($node); } /** * 删除一个节点及其子节点树 * * @param array $node * * @return boolean */ function remove(array $node) { $this->_dbo->startTrans(); $rst = false; do { $span = $node['right_value'] - $node['left_value'] + 1; $sql = "DELETE FROM {$this->tableName} " . "WHERE left_value >= {$node['left_value']} " . "AND right_value <= {$node['right_value']}"; $rst = $this->_dbo->execute($sql); if (!$rst) break; $sql = "UPDATE {$this->tableName} " . "SET left_value = left_value - {$span} " . "WHERE left_value > {$node['right_value']}"; $rst = $this->_dbo->execute($sql); if (!$rst) break; $sql = "UPDATE {$this->tableName} " . "SET right_value = right_value - {$span} " . "WHERE right_value > {$node['right_value']}"; $rst = $this->_dbo->execute($sql);// if (!$rst) break; } while(false); $this->_dbo->completeTrans(); return $rst; } /** * 删除一个节点及其子节点树 * * @param int $nodeId * * @return boolean */ function removeByPkv($nodeId) { $node = $this->find(array($this->primaryKey=>$nodeId)); if (!$node) { throw new NodeNotFoundException($nodeId); } return $this->remove($node); }/** * 通过路径查找分类: /图书/IT/技术/PHP * * @param string $name * @return array */function getByName($name){if (empty($name)) return null ;$path = trim($name);if (!preg_match('/^\//',$name)) $name = "/{$name}";if ($name === '/'){$name = $this->_rootNodeName ;$deep = 0;}else {$parts = normalize($path,'/');$deep = count($parts);$name = array_pop($parts);}return $this->find(array('name' => $name ,'deep' => $deep) );} function findAll($cond, $sort=null, $fields='*'){$sqlCond = CoreDbSqlHelper::parseConditions($this->_dbo,$cond);if ($sqlCond) $sqlCond = "WHERE {$sqlCond}";if ($sort) $sort = "ORDER BY {$sort}";$qfields = CoreDbSqlHelper::qfields($fields,$this->tableName);$sql = "SELECT {$qfields} FROM {$this->tableName} {$sqlCond} {$sort}";return $this->_dbo->getAll($sql);} /** * 返回根节点到指定节点路径上的所有节点 * * 返回的结果不包括“_#_ROOT_NODE_#_”根节点各个节点同级别的其他节点。 * 结果集是一个二维数组,可以用 array_to_tree() 函数转换为层次结构(树型)。 * * @param array $node * * @return array */ function getPath(array $node, $fields='*') { $inputarr = array($node['left_value'], $node['right_value']); $cond = CoreDbSqlHelper::bind($this->_dbo,'left_value < ? AND right_value > ?',$inputarr); $sort = 'left_value ASC'; $rowset = $this->findAll($cond, $sort, $fields); if (is_array($rowset)) { array_shift($rowset); } return $rowset; } /** * 取得下层分类到指定的上层分类的完整路径 * * 返回的结果不包括 $up 节点 * @param array $down * @param array $up * * @return array */ function getPartPath(array $down, array $up, $fields='*'){ //父分类的left_value小于子分类的left_value;父分类的right_value大于子分类的right_value $inputarr = array($up['left_value'], $down['left_value'],$down['right_value']); $cond = CoreDbSqlHelper::bind($this->_dbo,'left_value BETWEEN ? AND ? AND right_value > ?',$inputarr); $sort = "left_value ASC"; $rowset = $this->findAll($cond, $sort, $fields); if (is_array($rowset)) { array_shift($rowset); } return $rowset; } /** * 返回指定节点的直接子节点 * * @param array $node * * @return array */ function getSubNodes(array $node, $fields='*') { $inputarr = array($node[$this->primaryKey]); $cond = CoreDbSqlHelper::bind($this->_dbo,'parent_id = ?', $inputarr); $sort = 'left_value ASC'; return $this->findAll($cond, $sort, $fields); } /** * 返回指定节点为根的整个子节点树 * * @param array $node * * @return array */ function getSubTree(array $node, $fields='*') { $inputarr = array($node['left_value'], $node['right_value']); $cond = CoreDbSqlHelper::bind($this->_dbo,'left_value BETWEEN ? AND ?', $inputarr); $sort = 'left_value ASC'; return $this->findAll($cond, $sort, $fields); } /** * 获取指定节点同级别的所有节点 * * @param array $node * * @return array */ function getCurrentLevelNodes(array $node, $fields='*') { $cond = array('parent_id' => $node['parent_id']); $sort = 'left_value ASC'; return $this->findAll($cond, $sort, $fields); } /** * 取得所有节点 * * @return array */ function getAllNodes($fields='*') { return parent::findAll('left_value > 1', 'left_value ASC', $fields); } /** * 获取所有顶级节点(既 _#_ROOT_NODE_#_ 的直接子节点) * * @return array */ function getAllTopNodes($fields='*') { $cond = "parent_id = 0"; $sort = 'left_value ASC'; return $this->findAll($cond, $sort, $fields); } /** * 计算所有子节点的总数 * * @param array $node * * @return int */ function calcAllChildCount(array $node) { return intval(($node['right_value'] - $node['left_value'] - 1) / 2); }}
0 0
- 改进型先根遍历算法
- 改进型Clock算法
- 算法---冒泡法--改进型
- [二叉树] 先根遍历归递算法
- 二叉树的先根遍历,中根遍历,后根遍历的非递归算法
- 改进型归一化混音算法
- 递归算法先序遍历二叉树
- 先序遍历(非递归算法)
- 先序遍历的非递归算法
- 改进型clock算法--页面置换算法
- 树的先根遍历
- 算法马拉松28 先序遍历与后序遍历
- 先序遍历二叉树的递归算法
- 二叉树的先序遍历(非递归算法)
- 2叉树的先序遍历算法实现
- 先序遍历二叉树的递归算法怎样理解
- 先序遍历二叉树的递归算法怎样理解
- 二叉树的先序遍历(非递归算法)
- Android自动化测试工具实现简述
- JavaScript 基本语法知识
- EDID使用说明
- sfw 强悍的附件上传功能 -- 一周从无到有开发一个完整的企业站
- iNotify.js
- 改进型先根遍历算法
- 今天去 海图 买了本 mongodb 权威指南
- JS中用arguments能提取实参,形成数组,实例
- Android监听软键盘弹起+收回
- 发现市场上高厚的 linux 图书 不如以前的课本
- cocos2d-x-3.2编译慢之解决方案
- hibernate -- HQL语句总结
- 打QQ斗地主 打出了 11520 倍哦
- 改行熟悉运维知识