数据结构表结构介绍:
程序设计过程中,我们常常用树形结构来表征某些数据的关联关系,如企业上下级部门、栏目结构、商品,省份存储,分类等等,通常而言,这些树状结构需要借助于数据库完成持久化。然而目前的各种基于关系的数据库,都是以二维表的形式记录存储数据信息,因此是不能直接将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);
}
}