利用MySQL的表实现树的构建以及优化(php代码)

13 篇文章 0 订阅

数据结构表结构介绍:

程序设计过程中,我们常常用树形结构来表征某些数据的关联关系,如企业上下级部门、栏目结构、商品,省份存储,分类等等,通常而言,这些树状结构需要借助于数据库完成持久化。然而目前的各种基于关系的数据库,都是以二维表的形式记录存储数据信息,因此是不能直接将Tree存入DBMS,设计合适的Schema及其对应的CRUD算法是实现关系型数据库中存储树形结构的关键。理想中树形结构应该具备如下特征:数据存储冗余度小、直观性强;检索遍历过程简单高效;节点增删改查CRUD操作高效。

 

这种方案的优点很明显:设计和实现自然而然,非常直观和方便。缺点当然也是非常的突出:由于直接地记录了节点之间的继承关系,因此对Tree的任何CRUD操作都将是低效的,这主要归根于频繁的“递归”操作,递归过程不断地访问数据库,每次数据库IO都会有时间开销。当然,这种方案并非没有用武之地,在Tree规模相对较小的情况下,我们可以借助于缓存机制来做优化,将Tree的信息载入内存进行处理,避免直接对数据库IO操作的性能开销。

 

  在基于数据库的一般应用中,查询的需求总要大于删除和修改。为了避免对于树形结构查询时的“递归”过程,基于Tree的前序遍历设计一种全新的无递归查询、无限分组的左右值编码方案,来保存该树的数据。

第一次看见这种表结构,相信大部分人都不清楚左值(Lft)和右值(Rgt)是如何计算出来的,而且这种表设计似乎并没有保存父子节点的继承关系。但当你用手指指着表中的数字从1数到18,你应该会发现点什么吧。对,你手指移动的顺序就是对这棵树进行前序遍历的顺序,如下图所示。当我们从根节点Food左侧开始,标记为1,并沿前序遍历的方向,依次在遍历的路径上标注数字,最后我们回到了根节点Food,并在右边写上了18。

 

依据此设计,我们可以推断出所有左值大于2,并且右值小于11的节点都是Fruit的后续节点,整棵树的结构通过左值和右值存储了下来。然而,这还不够,我们的目的是能够对树进行CRUD操作,即需要构造出与之配套的相关算法。

php构建树:

 

class HtCommonTree 
{
    /**
     * @var $indexCount array   当前索引数
     */
    private $indexCount = 1;
    /**
     * @var $sourceList array   原始数据(普通数组形式)
     */
    public $sourceList;

    /**
     * @var $sourceTree array   原始数据(树结构)
     */
    public $sourceTree;

    /**
     * @var $subtreeIndexName string 子树索引名称
     */
    public $subtreeIndexName;

    /**
     * @var $middleNodeName string   中间节点key名称(数组结构时)
     */
    public $middleNodeName;

    /**
     * @var $leftNodeName string   左节点 key名称(数组结构时)
     */
    public $leftNodeName;

    /**
     * @var $rightNodeName string   右节点 key名称(数组结构时)
     */
    public $rightNodeName;


    /**
     * @var $walkTreeError array   填充节点错误数据集
     */
    public $walkTreeError;


    /**
     * 验证提交的form表单
     *
     * @return array
     */
    public function rules()
    {
        $rulesArray = parent::rules();
//        $rulesArray[] = [['agent_id', 'msg'], 'required'];
//        $rulesArray[] = [['userid_list', 'dept_id_list', 'to_all_user', 'msg'], 'customValidationCityCode', 'skipOnEmpty' => false];
        return $rulesArray;
    }

    /**
     * 将数组结构转为树结构
     * @param $source array 源数据
     * @return object
     */
    public function listToTree($source = [])
    {
        $nested = array();
        $source = $source ? $source : $this->sourceList;
        if (!is_array($source) || !$source) {
            return FunctionHelper::returnData(false, '源数据为空或者类型不正确');
        }

        if (
            !isset($this->middleNodeName)
            || !$this->middleNodeName
            || !isset($source[0][$this->middleNodeName])
        ) {
            return FunctionHelper::returnData(false, '生成树缺少根节点名称(key)');
        }

        if (
            !isset($this->subtreeIndexName)
            || !$this->subtreeIndexName
            || !isset($source[0][$this->subtreeIndexName])
        ) {
            return FunctionHelper::returnData(false, '生成树缺少树的索引名称(key)');
        }

        $source = array_column($source, NULL, $this->subtreeIndexName);

        foreach ($source as &$s) {
            if (is_null($s[$this->middleNodeName]) || $s[$this->middleNodeName] == '0') {
                //不同的名字访问同一个变量内容,使$nested访问顶级根节点
                $nested[] = &$s;
            } else {
                $pid = $s[$this->middleNodeName];
                if (isset($source[$pid])) {
                    if (!isset($source[$pid]['_children'])) {
                        $source[$pid]['_children'] = array();
                    }
                    $source[$pid]['_children'][] = &$s;
                }
            }
        }
        $this->sourceTree = $nested;
        return FunctionHelper::returnData(true, '成功', $nested);
    }

    /**
     * 填充左右节点
     * @param $_record array  一整棵树
     * @param $level int 位于树的第几层
     * @return object
     */
    private function walkTree(&$_record, $level)
    {
        if (
            !isset($_record[$this->leftNodeName])
            || !isset($_record[$this->rightNodeName])
            || !isset($_record[$this->subtreeIndexName])
        ) {
            $this->walkTreeError[] = print_r($_record, 1) . "-生成树缺失左右节点名称/子树索引名称";
        }

        $left = $this->indexCount;
        $this->indexCount += 1;
        if (isset($_record['_children']) && $_record['_children']) {
            foreach ($_record['_children'] as &$_record_child) {
                $this->walkTree($_record_child, $level + 1);
            }
        }
        $right = $this->indexCount;
        $this->indexCount += 1;
        $_record[$this->leftNodeName] = $left;
        $_record[$this->rightNodeName] = $right;
        $_record['level'] = $level;
        $this->sourceList[$_record[$this->subtreeIndexName]][$this->leftNodeName] = $_record[$this->leftNodeName];
        $this->sourceList[$_record[$this->subtreeIndexName]][$this->rightNodeName] = $_record[$this->rightNodeName];
        $this->sourceList[$_record[$this->subtreeIndexName]]['level'] = $_record['level'];
    }

    /**
     * 执行填充左右节点操作
     * @return array
     */
    public function convert()
    {
        if (
            !$this->leftNodeName
            || !$this->rightNodeName
            || !$this->subtreeIndexName
        ) {
            return FunctionHelper::returnData(false, '填充左右节点-生成树缺失左右节点名称/子树索引名称');
        }

        $this->walkTree($this->sourceTree[0], 1);
        //检验填充过程中是否出错
        if (isset($this->walkTreeError)) {
            return FunctionHelper::returnData(false, '填充左右节点失败-' . print_r($this->walkTreeError, 1));
        }
        return FunctionHelper::returnData(true, '成功', $this->sourceTree);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值