【mysql算法】在数据库中储存树形结构

news2024/11/26 8:50:36

【mysql&算法】在数据库中储存树形结构

  • 【一】常见的使用树的场景
  • 【二】方式一:邻接表
    • (1)方法介绍
    • (2)优点
    • (3)缺点
    • (4)实现案例:生成菜单树结构
  • 【三】方式二:子集
    • (1)方法介绍
    • (2)优点
    • (3)缺点
  • 【四】方式三:嵌套集
    • (1)方法介绍
    • (2)优点
    • (3)缺点
  • 【五】方式四:物化路径
    • (1)方法介绍

【一】常见的使用树的场景

以树状结构表示和存储数据是软件开发中的常见问题:
(1)XML / Markup语法解析器(例如Apache Xerces和Xalan XSLt)使用树
(2)PDF使用以下树结构:根节点->目录节点->页面节点->子页面节点。通常,PDF文件在内存中表示为平衡树
(3)多种科学和游戏应用程序使用决策树,概率树,行为树;Flare可视化库(http://flare.prefuse.org/)充分利用了生成树,经纪人在决定是否投标合同时也使用树

树是表示层次结构的方法之一,因此可用于许多问题域:
(1)计算机科学中的二叉树
(2)生物学中的系统树
(3)商业中的庞氏骗局
(4)项目管理中的任务分解树
(5)树用于建模多级关系,例如“上级-下级”,“祖先-后代”,“整个-部分”,“一般-特定”

【二】方式一:邻接表

(1)方法介绍

图论中的邻接表是一种通过存储每个顶点的邻居列表(即相邻顶点)来表示图的方法。对于树,可以仅存储父节点,然后每个列表都包含一个值,该值可以与顶点一起存储在数据库中。这是最流行的表示形式之一,也是最直观的表示形式:表仅具有对自身的引用(图2)。然后,根节点NULL的父节点包含一个空值()。
在这里插入图片描述
此方法的主要数据选择操作要求DBMS支持递归查询。PostgreSQL支持这种类型的查询,但是对于不支持DBMS的用户,可能需要通过使用临时表和存储过程来执行选择。以下是一些查询示例。

(1)获取给定节点的子树

WITH RECURSIVE sub_category(code, name, parent_code, level) AS (
	SELECT code, name, parent_code, 1 FROM goods_category WHERE code=1 /* id of the given node */
	UNION ALL 
	SELECT c.code, c.name, c.parent_code, level+1
	FROM goods_category c, sub_category sc
	WHERE c.parent_code = sc.code  
)
SELECT code, name, parent_code, level FROM sub_category;

(2)从根到给定节点的路径

WITH RECURSIVE sub_category(code, name, parent_code, level) AS (
	SELECT code, name, parent_code, 1 FROM goods_category WHERE code=5 /* id of the given node */
	UNION ALL 
	SELECT c.code, c.name, c.parent_code, level+1
	FROM goods_category c, sub_category sc
	WHERE c.code = sc.parent_code  
)
SELECT code, name, parent_code, (SELECT max(level) FROM sub_category) - level AS distance FROM sub_category;

(3)检查Cement节点(代码= 5)是否为Construction Material / Fixtures(代码= 1)的后代

WITH RECURSIVE sub_category(code, name, parent_code, level) AS (
	SELECT code, name, parent_code, 1 FROM goods_category WHERE code=5 /* id of the checked node */
	UNION ALL 
	SELECT c.code, c.name, c.parent_code, level+1
	FROM goods_category c, sub_category sc
	WHERE c.code = sc.parent_code  
) 
SELECT CASE WHEN exists(SELECT 1 FROM sub_category WHERE code=2 /* code of the subtree root */)
	THEN 'true' 
	ELSE 'false' 
	END

(2)优点

(1)数据的直观表示(元组(code, parent_code)直接对应于树的边缘);
(2)没有数据冗余,也不需要非参照完整性约束(除非您需要DBMS级约束来防止循环);
(3)树木可以有任意深度;
(4)快速,简单的插入,移动和删除操作,因为它们不会影响任何其他节点。

(3)缺点

该方法需要递归以便选择节点的后代或祖先,定义节点的深度,并找出后代的数量。如果DBMS不支持特定功能,则无法有效地实现这些操作。对于无法进行递归或不可行的情况,可以通过使用一个递归来实现此方法的某些优点。

(4)实现案例:生成菜单树结构

数据库的存储方式
在这里插入图片描述
后端代码实现

public static void main(String[] args) {
        Node[] nodes = new Node[]{
            new Node(1, 0, "123"),
            new Node(2, 1, "1234"),
            new Node(3, 1, "1235"),
            new Node(4, 3, "1236"),
            new Node(5, 2, "1237"),
        };
        print(Arrays.asList(nodes));
    }
    
    public static void print(List<Node> list) {
        // 节点信息映射
        Map<Integer, Node> nodeInfoMap = new HashMap<>();
        // 每个父节点对应的子节点列表
        Map<Integer, List<Integer>> childrenIdMap = new HashMap<>();
        List<Node> curLevelNodes = new ArrayList<>();
        for (Node node : list) {
            nodeInfoMap.put(node.id, node);
            if (node.parentId == 0) {
                curLevelNodes.add(node);
            } else {
                List<Integer> children = childrenIdMap.get(node.parentId);
                if (children == null) {
                    children = new ArrayList<>();
                    childrenIdMap.put(node.parentId, children);
                }
                children.add(node.id);
            }
        }
        for (Node node : curLevelNodes) {
            printRecursively(nodeInfoMap, childrenIdMap, node, "");
        }
    }
    
    private static void printRecursively(Map<Integer, Node> nodeInfoMap,
                                  Map<Integer, List<Integer>> childrenIdMap,
                                  Node node, String prefix) {
        System.out.println(prefix + node.name);
        List<Integer> children = childrenIdMap.get(node.id);
        if (children != null) {
            for (Integer childId : children) {
                printRecursively(nodeInfoMap, childrenIdMap, nodeInfoMap.get(childId), prefix + " ");
            }
        }
    }
    
    @AllArgsConstructor
    private static class Node {
        int id;
        int parentId;
        String name;
    }

【三】方式二:子集

(1)方法介绍

也称为Closure table或Bridge table,该方法将树表示为嵌套的子集:根节点包含所有深度为1的节点,它们依次包含深度为2的节点,依此类推

在这里插入图片描述
Subsets方法需要两个表:第一个表包含所有子集(表goods_category,图4),第二个表包含子集的包含事实,以及相应的深度级别(表subset,图4)。
在这里插入图片描述
结果,对于树中的每个节点,该subset表包含的记录数等于该节点的深度级别。因此,记录数随着算术级数的增长而增加,但是普通查询变得比以前的方法更简单,更快。

(1)获取给定节点的子树:

SELECT name, set_code, subset_code, level FROM subset s
    LEFT JOIN goods_category c ON c.code = s.set_code
WHERE subset_code = 1 /* the subtree root */
ORDER BY level;

(2)从根到给定节点的路径:

SELECT name, set_code, subset_code, level FROM subset s
    LEFT JOIN goods_category c ON c.code = s.subset_code
WHERE set_code = 3 /* the give node */
ORDER BY level;

(3)检查“块”节点(代码= 3)是否为“建筑材料/夹具”(代码= 1)的后代:

SELECT CASE 
    WHEN exists(SELECT 1 FROM subset 
                WHERE set_code = 3 AND subset_code = 1)
	THEN 'true' 
	ELSE 'false' 
	END

为了保持完整性,移动和插入操作需要实现触发器,该触发器将更新所有关联的记录。

(2)优点

Subsets方法的主要优点\是支持快速,简单,非递归的树操作:子树选择,祖先选择,节点深度级别的计算和后代数量。

(3)缺点

(1)相对不直观的数据表示(由于间接后代之间的冗余引用而变得复杂);
(2)数据冗余;
(3)数据完整性需要触发器;
(4)移动和插入操作比邻接列表方法更为复杂。插入需要为每个后代节点附加记录,而移动节点需要更新其后代和祖先的记录。

【四】方式三:嵌套集

(1)方法介绍

此方法的思想是存储树的前缀遍历。在此首先访问树的根,然后按前缀顺序访问左子树的所有节点,然后再次按前缀顺序访问右子树的节点。遍历的顺序存储在两个附加字段中:left_key和right_key。该left_key字段包含进入子树之前的遍历步骤数,而right_key包含离开子树之前的遍历步骤数。结果,对于每个节点,其后代的节点号在节点号之间,而与它们的深度级别无关。此属性允许编写查询而无需采用递归。

在这里插入图片描述
(1)获取给定节点的子树:

WITH root AS (SELECT left_key, right_key FROM goods_category WHERE code=2 /* id of the node */)
SELECT * FROM goods_category
    WHERE left_key >= (SELECT left_key FROM root) 
        AND right_key <= (SELECT right_key FROM root)
ORDER BY level;

(2)从根到给定节点的路径:

WITH node AS (SELECT left_key, right_key FROM goods_category WHERE code=8 /* id of the node */)
SELECT * FROM goods_category
    WHERE left_key <= (SELECT left_key FROM node) 
        AND right_key >= (SELECT right_key FROM node)
ORDER BY level;

(3)检查“块”节点是否是“建筑材料/夹具”节点的后代:

SELECT CASE 
    WHEN exists(SELECT 1 from goods_category AS gc1, goods_category gc2 
        WHERE gc1.code = 1 
            AND gc2.code = 3 
            AND gc1.left_key <= gc2.left_key 
            AND gc1.right_key >= gc2.right_key)
    THEN 'true'
    ELSE 'false'
END

(2)优点

它允许快速而简单的操作来选择节点的祖先,后代,其计数和深度级别,而无需递归。

(3)缺点

在插入或移动节点时必须重新分配遍历顺序。例如,为了将一个节点添加到最底层,有必要将所有节点的left_keyandright_key字段更新为其“ right and above”,这可能需要重新遍历整个树。可以通过分配给减少更新的数量left_key和right_key具有间隔,例如值10 000和200 000代替的1和20。这将允许插入节点而无需对其他节点重新编号。另一种方法是对left_key和的值使用实数right_key。

【五】方式四:物化路径

(1)方法介绍

也称为沿袭列,此方法的想法是显式存储从根开始的整个路径作为节点的主键(图6)。物化路径是表示树的一种优雅方式:每个节点都有一个直观的标识符,其中的各个部分都有明确定义的语义。此属性对于通用分类非常重要,包括国际疾病分类(ICD),科学论文中使用的通用小数分类(UDC),PACS(物理和天文学分类方案)。此方法的查询很简洁,但并不总是有效的,因为它们涉及子字符串匹配。

在这里插入图片描述
(1)获取给定节点的子树:

SELECT * FROM goods_category WHERE path LIKE '1.1%' ORDER BY path

(2)从根到给定节点的路径:

SELECT * FROM goods_category WHERE '1.1.3' LIKE path||'%' ORDER BY path

(3)检查“水泥”节点是否为“建筑材料/夹具”节点的后代:

SELECT CASE 
    WHEN exists(SELECT 1 FROM goods_category AS gc1, goods_category AS gc2
        WHERE gc1.name = 'Construction Material/Fixtures' 	
            AND gc2.name = 'Cement' 
            AND gc2.path LIKE gc1.path||'%')
    THEN 'true'
    ELSE 'false'
END

对于事先知道级别数和每个级别上的节点数的情况,可以通过使用带有严格定义的零填充小数位组的数字标识符来避免使用显式分隔符。这种方法被用于许多分类中,包括OKATO,NAICS。

也可以将单独的列用于层次结构的各个级别(多个谱系列):这将消除使用子字符串匹配的必要性。

物化路径方法的优点是数据的直观表示以及某些常见查询的简单性。

该方法的缺点包括复杂的插入,移动和删除操作,完整性约束的简单实现以及由于需要进行子字符串匹配而导致的查询效率低下。在使用数字标识符方法的情况下,另一个可能的缺点是深度级别的数量有限。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/877972.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

盒子阴影效果与环绕阴影

box-shadow 在前端样式里面&#xff0c;最常见的一中效果之一就是阴影&#xff0c;好的阴影可以瞬间给人一种高端的用户体验&#xff0c;今天简单总结下这个样式的语法与使用方法。 语法 box-shadow的语法其实是比较简单好记的&#xff0c;我们按照最全面的写法来看 x轴偏移…

社区团购商城拼团秒杀接龙分销团长小程序开源版开发

社区团购商城拼团秒杀接龙分销团长小程序开源版开发 功能介绍&#xff1a; 商品管理&#xff1a;增加商品-商品列表-商品分类-商品单/多规格-商品标签 订单管理&#xff1a;订单列表-订单挑选-订单导出-订单打印-批量发货-商品评价 会员管理&#xff1a;会员列表-会员挑选-会员…

1. 基于UDP的TFTP文件传输上传下载完整版本

1&#xff09;tftp协议概述 简单文件传输协议&#xff0c;适用于在网络上进行文件传输的一套标准协议&#xff0c;使用UDP传输 特点&#xff1a; 是应用层协议 基于UDP协议实现 数据传输模式 octet&#xff1a;二进制模式&#xff08;常用&#xff09; mail&#xff1a;…

jenkins 安装nodejs 14

参考&#xff1a; jenkins容器安装nodejs-前端问答-PHP中文网

微服务08-多级缓存

1.什么是多级缓存 传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,如图: 存在下面的问题: •请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈 •Redis缓存失效时,会对数据库产生冲击 多级缓存就是充分利用请求处理的每个环节,分…

VB6查表法编解Modbus RTU协议CRC16校验码

Modbus RTU协议CRC16编解码用VB6写起来比较啰嗦&#xff0c;需要做一些简单处理。下面就查表法&#xff0c;贴上源代码&#xff0c;并做一些简要说明。 源程序&#xff0c;对照上面的图看更方便。 Private Sub Command2_Click() Dim I As Integer, J As Integer Dim CRCHi As …

部署lawyer-llama

Git - Downloading PackageGit - Downloading PackageGit - Downloading Package 下载git&#xff0c;wget需要下载一下 &#xff08;GNU Wget 1.21.4 for Windows&#xff09;&#xff0c; Windows中git bash完全可以替代原生的cmd&#xff0c;但是对于git bash会有一些Linu…

HTTP之cookie基础学习

目录 Cookie 什么是Cookie Cookie分类 Cookie版本 Cookie工作原理 Cookie详解 创建cookie cookie编码 cookie过期时间选项 Cookie流程 Cookie使用 会话管理 个性化信息 记录用户的行为 Cookie属性 domain选项 path选项 secure选项 cookie…

【日常积累】RPM包依赖下载及私有yum仓库搭建

概述 某些时候&#xff0c;我们需要下载某个RPM包依赖的依赖。如某些内网环境&#xff0c;就需要自行准备rpm包。可以通过能上互联网的服务器进行相应的rpm包下载&#xff0c;然后在拷贝到相应的服务器安装&#xff0c;或者搭建自己的内容rpm包仓库。 查看*.rpm 包依赖&#…

修改el-tooltip组件的背景色

修改el-tooltip组件的背景色 // 提示气泡的背景色 .el-tooltip__popper{background-color: pink !important; } .popper__arrow {border-top-color: pink !important; } .popper__arrow:after {border-top-color: pink !important; }

基于通达信量化接口会实现自动交易吗?(股票自动下单接口)

通常情况下&#xff0c;在开发股票交易接口时&#xff0c;会包含多个接口功能的研发&#xff0c;因此通达信量化接口可以实现自动化交易。即通过通达信的API接口&#xff08;股票自动下单接口&#xff09;&#xff0c;可以实现与交易所的连接和交互&#xff0c;包括下单、撤单、…

Vue3 引用第三方Swiper内容触摸滑动简单应用

去官网查看更多教程→&#xff1a;Swiper官网 → 点击教程在vue中使用Swiper→ 在Vue中使用Swiper cd 到项目 安装Swiper&#xff1a; cnpm install --save swiper 安装指定版本 cnpm install --save swiper8.1.6 9.4.1 10.1.0…

【CI/CD】Rancher K8s

Rancher & K8s Rancher 和 K8s 的关系是什么&#xff1f;K8s 全称为 Kubernetes&#xff0c;它是一个开源的&#xff0c;用于管理云平台中多个主机上的容器化的应用。而 Rancher 是一个完全开源的企业级多集群 Kubernetes 管理平台&#xff0c;实现了 Kubernetes 集群在混合…

OpenCV图像处理——边缘检测

目录 原理Sobel检测算子方法应用 Laplacian算子Canny边缘检测原理 原理 Sobel检测算子 方法 应用 sobel_x_or_ycv.Sobel(src,ddepth,dx,dy,dst,ksize,scale,delta,borderType)import numpy as np import cv2 as cv import matplotlib.pyplot as pltimgcv.imread(./汪学长的随堂…

Python学习 -- 常用函数与实例详解

在Python编程中&#xff0c;数据转换是一项关键任务&#xff0c;它允许我们在不同数据类型之间自由流动&#xff0c;从而提高代码的灵活性和效率。本篇博客将深入探讨常用的数据转换函数&#xff0c;并通过实际案例为你展示如何巧妙地在不同数据类型之间转换。 数据类型转换函…

红日ATT&CK VulnStack靶场(三)

网络拓扑 web阶段 1.扫描DMZ机器端口 2.进行ssh和3306爆破无果后访问web服务 3.已知目标是Joomla&#xff0c;扫描目录 4.有用的目录分别为1.php 5.configuration.php~中泄露了数据库密码 6.administrator为后台登录地址 7.直接连接mysql 8.找到管理员表&#xff0c;密码加密了…

日常BUG—— SpringBoot项目DEBUG模式启动慢、卡死。

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;日常BUG、BUG、问题分析☀️每日 一言 &#xff1a;存在错误说明你在进步&#xff01; 一、问题描述 我们调试程序时&#xff0c;需要使用DEBUG模式启动SpringBoot项目&#xff0c; 有时候会发…

【C++入门到精通】C++入门 —— list (STL)

阅读导航 前言一、list简介1.概念2.特点 二、list的使用1.list的构造2.常见的操作⭕std::list类型的增、删、查、改 三、list与vector的对比温馨提示 前言 文章绑定了VS平台下std::list的源码&#xff0c;大家可以下载了解一下&#x1f60d; 前面我们讲了C语言的基础知识&…

如何做好一名网络工程师?具体实践?

预防问题 – 资格与认证 在安装线缆或升级网络时测试线缆是预防问题的有效方式。对已安装布线进行测试的方法有两种。 资格测试确定布线是否有资格执行某些操作 — 换言之&#xff0c;支持特定网络速度或应用。尽管“通过”认证测试也表明按标准支持某一网络速度或应用的能力…

国企的大数据岗位方向的分析

现如今大数据已无所不在&#xff0c;并且正被越来越广泛的被应用到历史、政治、科学、经济、商业甚至渗透到我们生活的方方面面中&#xff0c;获取的渠道也越来越便利。 今天我们就来聊一聊“大屏应用”&#xff0c;说到大屏就一定要聊到数据可视化&#xff0c;现如今&#xf…