说明:
每个数据项都有自己的左值和右值。所有数据的左右值是连续的数字。
① 当右值比左值大1时,表示该数据无子集;
② 当某一数据项的左值大于另一数据项的左值,且该数据项的右值小于同另一数据项的右值时,该数据项属于另一数据项的子集;
例如:中国(1,8) 广东(2,5) 珠海(3,4) 江西(6,7)
数据库表结构:
CREATE TABLE `jd_category` (
`id_category` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(24) NOT NULL,
`lft` int(10) NOT NULL,
`rgt` int(10) NOT NULL,
`level` tinyint(1) NOT NULL,
`is_del` tinyint(1) NOT NULL,
PRIMARY KEY (`id_category`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
BTreeR 只用于实现数据读取:
'level',
'lft' => 'lft',
'rgt' => 'rgt',
);
/**
* 获取虚拟的顶级目录
*/
protected function virtualTop(){
return array(
self::$fieldMap['level'] => -1,
self::$fieldMap['lft'] => -1,
self::$fieldMap['rgt'] => ((int)$this->where($this->condition)->max('rgt') + 1),
);
return $this->obj;
}
// 设置当前对象
public function setObj( $obj ){
if( $obj[ $this->getPk() ] && isset($obj[ self::$fieldMap['lft'] ]) && isset($obj[ self::$fieldMap['rgt'] ]) && isset($obj[ self::$fieldMap['level'] ]) ){
$this->obj = $obj;
}else{
$this->obj = false;
}
return $this;
}
// 设置条件参数
public function setCondition( $condition ){
$this->condition = $condition;
return $this;
}
/**
* 是否存在父节点
*/
public function hasParent(){
if( !$this->obj[ $this->getPk() ] || !$this->obj[ self::$fieldMap['level'] ] ){
return false;
}
return true;
}
/**
* 查询父级节点
*
* @param boolean $width_self 是否包含当前节点
*/
public function parents( $width_self = false ){
if( !$this->obj[ $this->getPk() ] ){
return false;
}
$map = $this->condition;
if( $width_self ){
$map[ self::$fieldMap['lft'] ] = array('elt', $this->obj[ self::$fieldMap['lft'] ]);
$map[ self::$fieldMap['rgt'] ] = array('egt', $this->obj[ self::$fieldMap['rgt'] ]);
}else{
$map[ self::$fieldMap['lft'] ] = array('lt', $this->obj[ self::$fieldMap['lft'] ]);
$map[ self::$fieldMap['rgt'] ] = array('gt', $this->obj[ self::$fieldMap['rgt'] ]);
}
$this->where($map)->order( self::$fieldMap['lft'].' asc' );
return $this;
}
/**
* 是否存在子节点
*/
public function hasSub(){
if( $this->obj[ $this->getPk() ] && ($this->obj[ self::$fieldMap['rgt'] ] - $this->obj[ self::$fieldMap['lft'] ] == 1) ){
return false;
}
return true;
}
/**
* 查询子节点
* @param boolean $width_self 是否包含当前节点
*/
public function subs( $width_self = false ){
$map = $this->condition;
if( $this->obj[ $this->getPk() ] ){
if( $width_self ){
$map[ self::$fieldMap['lft'] ] = array('egt', $this->obj[ self::$fieldMap['lft'] ]);
$map[ self::$fieldMap['rgt'] ] = array('elt', $this->obj[ self::$fieldMap['rgt'] ]);
}else{
$map[ self::$fieldMap['lft'] ] = array('gt', $this->obj[ self::$fieldMap['lft'] ]);
$map[ self::$fieldMap['rgt'] ] = array('lt', $this->obj[ self::$fieldMap['rgt'] ]);
}
}
$this->where($map)->order( self::$fieldMap['lft'].' asc' );
return $this;
}
/**
* 把返回的数据集转换成Tree
* @param array $list 数据集合
* @param string $child 存放子集的key
*/
public function toTree($list, $child = '_child' ){
// TODO
}
}
?>
BTreeW 继承子 BtreeR,用于实现数据操作:
condition;
if( !$this->obj[ $this->getPk() ] ){
$this->obj = $this->virtualTop();
}
$lft = $this->obj[ self::$fieldMap['rgt'] ];
$data[ self::$fieldMap['level'] ] = $this->obj[ self::$fieldMap['level'] ] + 1;
$data[ self::$fieldMap['lft'] ] = $lft;
$data[ self::$fieldMap['rgt'] ] = $lft + 1;
$this->startTrans();
$insert_id = $this->add($data);
if( !$insert_id ){
$this->rollback();
return false;
}
$map = $this->condition;
$map[ self::$fieldMap['lft'] ] = array('gt', $lft);
$res = $this->where( $map )->setInc( self::$fieldMap['lft'], 2 );
if( false === $res ){
$this->rollback();
return false;
}
$map = $this->condition;
$map[ $this->getPk() ] = array('neq', $insert_id);
$map[ self::$fieldMap['rgt'] ] = array('egt', $lft);
$res = $this->where( $map )->setInc( self::$fieldMap['rgt'], 2 );
if( false === $res ){
$this->rollback();
return false;
}
$this->commit();
return true;
}
/**
* 删除节点及所有子节点
* ① 删除当前节点及其所有子节点
* ② 更改父级右值-$move_count,更改父级右边记录左右值-$move_count
*
* @param string $field 标识删除的字段名
* @param int $value 标识删除的字段值
* @return array 所有删除的节点ID集
*/
public function delInBTree( $field = false, $value = 1 ) {
if( !$this->obj[ $this->getPk() ] ){
return false;
}
$map = $this->condition;
if( $this->obj[ self::$fieldMap['rgt'] ] - $this->obj[ self::$fieldMap['lft'] ] == 1 ){
$del_ids = $this->obj[ $this->getPk() ];
}else{
$map[ self::$fieldMap['lft'] ] = array('egt', $this->obj[ self::$fieldMap['lft'] ]);
$map[ self::$fieldMap['rgt'] ] = array('elt', $this->obj[ self::$fieldMap['rgt'] ]);
$del_ids = $this->where($map)->getField( $this->getPk(), true );
}
if( !$del_ids ){
return false;
}
$this->startTrans();
$this->where(array( $this->getPk() => array('in', $del_ids) ));
if($field){
$res = $this->setField( $field, $value );
}else{
$res = $this->delete();
}
if( !$res ){
$this->rollback();
return false;
}
$move_count = $this->obj[ self::$fieldMap['rgt'] ] - $this->obj[ self::$fieldMap['lft'] ] + 1;
$map = $this->condition;
$map[ self::$fieldMap['lft'] ] = array('gt', $this->obj[ self::$fieldMap['rgt'] ]);
$res = $this->where( $map )->setDec( self::$fieldMap['lft'], $move_count );
if( false === $res ){
$this->rollback();
return false;
}
$map = $this->condition;
$map[ self::$fieldMap['rgt'] ] = array('gt', $this->obj[ self::$fieldMap['rgt'] ]);
$res = $this->where( $map )->setDec( self::$fieldMap['rgt'], $move_count );
if( false === $res ){
$this->rollback();
return false;
}
$this->commit();
return $del_ids;
}
/**
* 节点左右移动
* ① 确定右左移动的节点
* ② 左节点集向右移动$r_move_count
* ③ 右节点集向左移动$l_move_count
*
* @param boolean $move_lft 是否是左移
*/
public function moveLOrRInBTree($move_lft = true){
if( !$this->obj[ $this->getPk() ] ){
return false;
}
$map = $this->condition;
$fields = $this->getPk().','.implode(',', self::$fieldMap);
if( $move_lft ){
$map[ self::$fieldMap['rgt'] ] = $this->obj[ self::$fieldMap['lft'] ] - 1;
$lft = $this->field($fields)->where($map)->find();
$rgt = $this->obj;
}else{
$lft = $this->obj;
$map[ self::$fieldMap['lft'] ] = $this->obj[ self::$fieldMap['rgt'] ] + 1;
$rgt = $this->field($fields)->where($map)->find();
}
if( !$lft || !$rgt ){
return false;
}
$l_move_count = $lft[ self::$fieldMap['rgt'] ] - $lft[ self::$fieldMap['lft'] ] + 1;
$r_move_count = $rgt[ self::$fieldMap['rgt'] ] - $rgt[ self::$fieldMap['lft'] ] + 1;
// 查询需要需要移动的ID集
$map = $this->condition;
if( $lft[ self::$fieldMap['rgt'] ] - $lft[ self::$fieldMap['lft'] ] == 1 ){
$lft_ids = $lft[ $this->getPk() ];
}else{
$map[ self::$fieldMap['lft'] ] = array('egt', $lft[ self::$fieldMap['lft'] ]);
$map[ self::$fieldMap['rgt'] ] = array('elt', $lft[ self::$fieldMap['rgt'] ]);
$lft_ids = $this->where($map)->getField( $this->getPk(), true );
}
if( $rgt[ self::$fieldMap['rgt'] ] - $rgt[ self::$fieldMap['lft'] ] == 1 ){
$rgt_ids = $rgt[ $this->getPk() ];
}else{
$map[ self::$fieldMap['lft'] ] = array('egt', $rgt[ self::$fieldMap['lft'] ]);
$map[ self::$fieldMap['rgt'] ] = array('elt', $rgt[ self::$fieldMap['rgt'] ]);
$rgt_ids = $this->where($map)->getField( $this->getPk(), true );
}
// 执行移动
$this->startTrans();
$res = $this->where( array($this->getPk() => array('in', $lft_ids)) )->save( array(
self::$fieldMap['lft'] => array('exp', self::$fieldMap['lft'].'+'.$r_move_count),
self::$fieldMap['rgt'] => array('exp', self::$fieldMap['rgt'].'+'.$r_move_count),
) );
if( false === $res ){
$this->rollback();
return false;
}
$res = $this->where( array($this->getPk() => array('in', $rgt_ids)) )->save( array(
self::$fieldMap['lft'] => array('exp', self::$fieldMap['lft'].'-'.$l_move_count),
self::$fieldMap['rgt'] => array('exp', self::$fieldMap['rgt'].'-'.$l_move_count),
) );
if( false === $res ){
$this->rollback();
return false;
}
$this->commit();
return true;
}
// TODO
public function moveLevelInBTree(){
}
}
?>