65.9K
CodeProject 正在变化。 阅读更多。
Home

无需递归实现二叉树查询上线/下线节点的数据结构实现

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (12投票s)

2010年11月3日

CPOL

4分钟阅读

viewsIcon

162167

可用于多层次营销的二叉树(可相应修改用于 n 叉树)。

引言

我们有时会面临存储二叉树数据并进行查询的问题。我们通常依赖递归(因此成本高)来查询树结构。这里有一个简单的数据结构更改,可以使用简单的 SQL 语句查询树结构 - 无需递归。

背景

在某些情况下,我们希望将二叉树的节点与它们的父节点一起存储在数据库中,并带有位置信息(在这种情况下,是父节点的左节点或右节点)。当我们要查询数据库以获取某个节点上线或下线节点,或者我们想获取二叉树中每个级别的节点计数时,问题就出现了。

您可以考虑在此代码示例的实现中,为多级营销公司创建数据结构来维护成员及其层级关系。

Using the Code

提出的解决方案包含 2 个数据表和 1 个触发器。

注意:下面使用的字段命名约定有点奇怪,但遵循以下规则 - 字段名称的前 5 个字符对应表名,接下来的 2 个字符定义数据类型,接下来的 2 个字符定义约束,其余的字符是字段名。

表 1

  • tbl_BinaryTree_Nodes,存储节点详情(例如成员详情)的主表

字段

  • Nodes_IN_PK_Code -- 表的主键
  • Nodes_IN_FK_ParentCode -- 父节点
  • Nodes_CH_xx_Position -- 父节点下的位置,左或右
  • Nodes_VC_xx_NodeData -- 节点数据 -(可以添加更多字段来容纳节点数据

表 2

  • tbl_BinaryTree_Hierarchy,存储节点层级信息的辅助表

字段

  • Hiera_IN_PK_Code -- 表的主键
  • Hiera_IN_FK_ParentCode -- 外键
  • Hiera_IN_FK_ChildCode -- 外键
  • Hiera_CH_xx_Position -- 父节点下的位置,左或右
  • Hiera_IN_xx_NodeLevel -- 树下的节点级别

触发器 1

trg_BinaryTree_Nodes_ADD,用于更新后续查询的树层级信息

创建表和触发器的 SQL 如下

Create Table tbl_BinaryTree_Nodes
(
    Nodes_IN_PK_Code Int Identity(1,1) Constraint PK_Nodes Primary Key,
    Nodes_IN_FK_ParentCode Int Constraint _
	FK_Nodes_To_Nodes References tbl_BinaryTree_Nodes (Nodes_IN_PK_Code ),
    Nodes_CH_xx_Position Char(1) Not Null Constraint CH_Nodes_Position Check _
	(Nodes_CH_xx_Position = 'L' OR Nodes_CH_xx_Position = 'R'),
    Nodes_VC_xx_NodeData Varchar(50)
)
GO

Create Table tbl_BinaryTree_Hierarchy
(
    Hiera_IN_PK_Code Int Identity(1,1) Constraint PK_Hiera Primary Key,
    Hiera_IN_FK_ParentCode Int Constraint _
	FK_Hiera_Nodes_Parent References tbl_BinaryTree_Nodes(Nodes_IN_PK_Code ),
    Hiera_IN_FK_ChildCode Int Constraint FK_Hiera_Nodes_Child References _
	tbl_BinaryTree_Nodes(Nodes_IN_PK_Code ),
    Hiera_CH_xx_Position  Char(1) Not Null Constraint CH_Hiera_Position Check _
	(Hiera_CH_xx_Position = 'L' OR Hiera_CH_xx_Position = 'R'),
    Hiera_IN_xx_NodeLevel int
)
GO

Create Trigger trg_BinaryTree_Nodes_ADD
On tbl_BinaryTree_Nodes
For Insert
AS
Begin
    IF (@@RowCount=1)
        Begin
            Insert Into tbl_BinaryTree_Hierarchy
                (Hiera_IN_FK_ParentCode, Hiera_IN_FK_ChildCode, _
		Hiera_CH_xx_Position, Hiera_IN_xx_NodeLevel)
            Select (Case When Hiera_IN_FK_ParentCode Is Null _
		Then Hiera_IN_FK_ChildCode Else Hiera_IN_FK_ParentCode  End), 
                    (Select Nodes_IN_PK_Code From Inserted), _
			(Case When Hiera_IN_FK_ParentCode Is Null _
			Then (Select Nodes_CH_xx_Position From Inserted) _
			Else Hiera_CH_xx_Position End) , 
                    (Hiera_IN_xx_NodeLevel + 1 )
                From tbl_BinaryTree_Hierarchy Where Hiera_IN_FK_ChildCode = _
			(Select Nodes_IN_FK_ParentCode From Inserted)
            Union ALL
            Select NULL , Nodes_IN_PK_Code , Nodes_CH_xx_Position , 1 From Inserted 
        End
    Else
        Begin
            RaisError ('Multiple Insertion Not Handled in Trigger to _
				update tbl_BinaryTree_Hierarchy',1,1)
            -- Code Using Cursor for Multiple insert
            --... 
            --...
            --...
            --
        End
End
GO 

SQL 查询

我们将使用示例数据填充数据库以进行查询。我们将创建一个如下所示的树

 --'------------------------------------------
--'          The Binary Tree Representation
--'------------------------------------------
--'                 RootNode
--'             --------|-------
--'             |              |
--'            AL1            AL2
--'       -----|-----
--'       |          |
--'      AL2        AR2
--'   ---|---
--'  |       |
--' AL3     AR3
--'------------------------------------------

生成上述树的 SQL 如下

Insert Into tbl_BinaryTree_Nodes (Nodes_IN_FK_ParentCode, Nodes_CH_xx_Position, _
	Nodes_VC_xx_NodeData) Values(Null,'L','Root Node')
GO
Insert Into tbl_BinaryTree_Nodes (Nodes_IN_FK_ParentCode, Nodes_CH_xx_Position, _
	Nodes_VC_xx_NodeData) Values(1,'L','AL1')
GO
Insert Into tbl_BinaryTree_Nodes (Nodes_IN_FK_ParentCode, Nodes_CH_xx_Position, _
	Nodes_VC_xx_NodeData) Values(1,'R','AR1')
GO
Insert Into tbl_BinaryTree_Nodes (Nodes_IN_FK_ParentCode, Nodes_CH_xx_Position, _
	Nodes_VC_xx_NodeData) Values(2,'L','AL2')
GO
Insert Into tbl_BinaryTree_Nodes (Nodes_IN_FK_ParentCode, Nodes_CH_xx_Position, _
	Nodes_VC_xx_NodeData) Values(2,'R','AR2')
GO
Insert Into tbl_BinaryTree_Nodes (Nodes_IN_FK_ParentCode, Nodes_CH_xx_Position, _
	Nodes_VC_xx_NodeData) Values(4,'L','AL3')
GO
Insert Into tbl_BinaryTree_Nodes (Nodes_IN_FK_ParentCode, Nodes_CH_xx_Position, _
	Nodes_VC_xx_NodeData) Values(4,'R','AR3') 
-- A. Get All Nodes directly under current Node
Select B.Nodes_VC_xx_NodeData, Hiera_IN_xx_NodeLevel From tbl_BinaryTree_Hierarchy H
Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ChildCode  = B.Nodes_IN_PK_Code
Where H.Hiera_IN_FK_ParentCode=1 

-- B. Get All Nodes directly under current Node in left side
Select B.Nodes_VC_xx_NodeData, Hiera_IN_xx_NodeLevel From tbl_BinaryTree_Hierarchy H
Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ChildCode  = B.Nodes_IN_PK_Code
Where H.Hiera_IN_FK_ParentCode=1 And H.Hiera_CH_xx_Position ='L'

-- C. Get All Nodes directly under current Node in right side
Select B.Nodes_VC_xx_NodeData, Hiera_IN_xx_NodeLevel From tbl_BinaryTree_Hierarchy H
Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ChildCode  = B.Nodes_IN_PK_Code
Where H.Hiera_IN_FK_ParentCode=1 And H.Hiera_CH_xx_Position ='R'

-- D. Get All Nodes directly above the current 
Select B.Nodes_VC_xx_NodeData, Hiera_IN_xx_NodeLevel From tbl_BinaryTree_Hierarchy H
Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ParentCode  = B.Nodes_IN_PK_Code
Where H.Hiera_IN_FK_ChildCode=7

-- E. Get count of Nodes at each level under a specific node
Select Hiera_IN_xx_NodeLevel, count(*) From tbl_BinaryTree_Hierarchy H
Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ChildCode  = B.Nodes_IN_PK_Code
Where H.Hiera_IN_FK_ParentCode=1 
Group By Hiera_IN_xx_NodeLevel

-- F. To find out the Nodes which got balanced due to new node entry
-- Lets us assume that the newly added node is AR3 - (which makes AL2 balanced)
-- We can use simple SQL Query or Common Table Expressions (CTE) 
-- on SQL 2005 to get the data
-- SQL Using Common Table Expressions (CTE) on SQL 2005
With CTE as (
Select B.Nodes_IN_PK_Code as PK, B.Nodes_VC_xx_NodeData as NodeData, _
	Hiera_IN_xx_NodeLevel as NodeLevel, 
      '' as IsBalanced,
   Power(2,Hiera_IN_xx_NodeLevel)-2 as TotalNodeForBalanceTree,
   (Select Count(Hiera_IN_PK_Code) From tbl_BinaryTree_Hierarchy _
	Where Hiera_CH_xx_Position = 'L' and _
	Hiera_IN_FK_ParentCode=B.Nodes_IN_PK_Code) _
	as TotalLeftChild,
   (Select Count(Hiera_IN_PK_Code) From tbl_BinaryTree_Hierarchy _
	Where Hiera_CH_xx_Position = 'R' and _
	iera_IN_FK_ParentCode=B.Nodes_IN_PK_Code) _
	as TotalRightChild
From tbl_BinaryTree_Hierarchy H 
Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ParentCode   = B.Nodes_IN_PK_Code

Where H.Hiera_IN_FK_ParentCode   is NOT NULL and H.Hiera_IN_FK_ChildCode = 7   
					-- (Primary key of Newly Added Node) 
)
Select CTE.PK, CTE.NodeData, CTE.NodeLevel,
Case   When ((CTE.TotalNodeForBalanceTree / 2 = TotalLeftChild) and _
	(CTE.TotalNodeForBalanceTree / 2 = TotalRightChild)) then _
	'Yes' Else' No' End As IsBalanced, 
CTE.TotalNodeForBalanceTree , CTE.TotalLeftChild , CTE.TotalRightChild
From CTE 

更多查询 - 2

 --'------------------------------------------
--' TREE 2
--'  The Binary Tree Representation  Tree - 2 
--'------------------------------------------
--'                 RootNode
--'             --------|-------
--'             |              |
--'            AL              AR
--'       -----|-----      -----|-----
--'       |          |     |          |
--'      ZL         ZR     BL        BR
--'                      ---|---   ---|---
--'                     |       |  |      |
--'                    CL      CR  DL     *DR*
--'------------------------------------------ 

A. 如何根据新节点插入查找平衡节点(参考 Tree -2)

假设新添加的节点是 *DR* -(这使得 AR、BR 平衡)。

我们可以使用简单的 SQL 查询或 SQL 2005 上的通用表表达式 (CTE) 来获取数据

With CTE as (
Select B.Nodes_IN_PK_Code as PK, B.Nodes_VC_xx_NodeData as NodeData, _
	Hiera_IN_xx_NodeLevel as NodeLevel, 
      '' as IsBalanced,
   Power(2,Hiera_IN_xx_NodeLevel)-2 as TotalNodeForBalanceTree,
   (Select Count(Hiera_IN_PK_Code) From tbl_BinaryTree_Hierarchy _
	Where Hiera_CH_xx_Position = 'L' and _
	Hiera_IN_FK_ParentCode=B.Nodes_IN_PK_Code) _
	as TotalLeftChild,
   (Select Count(Hiera_IN_PK_Code) From tbl_BinaryTree_Hierarchy _
	Where Hiera_CH_xx_Position = 'R' and _
	Hiera_IN_FK_ParentCode=B.Nodes_IN_PK_Code) _
	as TotalRightChild
From tbl_BinaryTree_Hierarchy H 
Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ParentCode   = _
	B.Nodes_IN_PK_Code

Where H.Hiera_IN_FK_ParentCode   _
is NOT NULL and H.Hiera_IN_FK_ChildCode = 7   -- (Primary key of Newly Added Node) 
)
Select CTE.PK, CTE.NodeData, CTE.NodeLevel,
Case   When ((CTE.TotalNodeForBalanceTree / 2 = TotalLeftChild) and _
	(CTE.TotalNodeForBalanceTree / 2 = TotalRightChild)) then 'Yes' Else' No' _
	End As IsBalanced, 
CTE.TotalNodeForBalanceTree , CTE.TotalLeftChild , CTE.TotalRightChild
From CTE

B. 查找节点的极端左叶和右叶(参考 Tree -2)

要在任何节点的左侧或右侧添加新节点,您只需知道相应侧的最后一个节点(叶子节点)。您可以使用简单的 SQL 查询来获取此信息。

例如,在上面的树中,根节点的极端右侧是 *DR*,极端左侧是 ZL。类似地,节点 AR 的极端右侧是 *DR*,极端左侧是 CL。

;with CTE (ExtremeNode, Position, NodeLevel  )As 
(
    Select Hiera_IN_FK_ChildCode, Hiera_CH_xx_Position, 1 from  tbl_BinaryTree_Hierarchy 
            where Hiera_IN_xx_NodeLevel=2 and Hiera_IN_FK_ParentCode = _
		?ParentNodePK?  and Hiera_CH_xx_Position = ?LegSide?
    UNION ALL
    Select Hiera_IN_FK_ChildCode, Hiera_CH_xx_Position , CTE.NodeLevel + 1   _
		from  tbl_BinaryTree_Hierarchy 
            INNER JOIN CTE  ON CTE.ExtremeNode = Hiera_IN_FK_ParentCode _
		and CTE.Position = Hiera_CH_xx_Position
            where Hiera_IN_xx_NodeLevel = 2

)
Select Top 1 *  From CTE order By NodeLevel Desc; 

C. 获取因新插入而成为平衡节点的节点列表(参考 Tree -2 和 MLM 佣金支付)

当 *DR* 被添加时,BR 和 AR 会获得报酬 - 但具体多少尚不清楚 - 根据一般的 MLM 趋势,我们可以计算出 BR 将获得 1 个对的收益,AR 将获得 1 个对的收益。

我们需要找出相对于新添加节点的父节点列表,这些节点将获得收益。

现在,获取需要支付报酬的节点列表如下。

下面的 SQL 查询将为您提供所有父节点的列表,最后一列 ShouldIPay Pay DoNotPay)指示哪个父节点需要支付报酬(根据需要修改字段列表)。

With CTE as (
  Select B.Nodes_IN_PK_Code as PK, B.Nodes_VC_xx_NodeData as NodeData, _
	Hiera_IN_xx_NodeLevel as NodeLevel, _
	Hiera_CH_xx_Position as Position, '' as IsBalanced,
    Power(2,Hiera_IN_xx_NodeLevel)-2 as TotalNodeForBalanceTree,
    (Select Count(Hiera_IN_PK_Code) From tbl_BinaryTree_Hierarchy _
	Where Hiera_CH_xx_Position = 'L' and _
	Hiera_IN_FK_ParentCode=B.Nodes_IN_PK_Code) as TotalLeftChild,
    (Select Count(Hiera_IN_PK_Code) From tbl_BinaryTree_Hierarchy _
	Where Hiera_CH_xx_Position = 'R' and _
	Hiera_IN_FK_ParentCode=B.Nodes_IN_PK_Code) as TotalRightChild
  From tbl_BinaryTree_Hierarchy H 
  Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ParentCode   = _
	B.Nodes_IN_PK_Code
  Where H.Hiera_IN_FK_ParentCode   is NOT NULL and _
	H.Hiera_IN_FK_ChildCode = ?PKofNewnode?   -- (Primary key of Newly Added Node) 
)
Select CTE.PK, CTE.NodeData, CTE.NodeLevel, CTE.Position,
Case   When ((CTE.TotalNodeForBalanceTree / 2 = TotalLeftChild) _
	and (CTE.TotalNodeForBalanceTree / 2 = TotalRightChild)) _
	then 'Yes' Else' No' End As IsBalanced, 
CTE.TotalNodeForBalanceTree , CTE.TotalLeftChild , CTE.TotalRightChild,
Case   When Position ='L' then (Case When TotalLeftChild<=TotalRightChild _
	Then 'PAY' Else 'Do Not Pay' End ) 
       When Position ='R' then (Case When TotalLeftChild>=TotalRightChild _
	Then 'PAY' Else 'Do Not Pay' End ) 
        End As ShouldIPay
From CTE 

这些表可以轻松修改以存储有关节点信息的附加信息,但基本字段将保持不变。

目前,触发器仅针对单次插入编写,我们可以修改触发器以处理批量插入。我们需要编写触发器来处理节点移动,以修改层级表。

历史

  • 修订 1:添加了更多查询 - 2 个部分
© . All rights reserved.