树结构解析及python实现

定义:

树(数据结构)

不要与特定类型的树数据结构trie混淆。

不要与树(图论)混淆,这是一种特定类型的数学对象。

 

一棵简单的无序树; 在此图中,标记为7的节点有两个子节点,标记为2和6,一个父节点标记为2.根节点位于顶部,没有父节点。

在计算机科学中,是一种广泛使用的抽象数据类型(ADT) - 或实现此ADT的数据结构 - 模拟分层树结构,具有父节点的子值的子值和子树,表示为一组链接节点。

树数据结构可以递归地定义为节点集合(从根节点开始),其中每个节点是由值组成的数据结构,以及对节点(“子”)的引用列表,其中没有引用重复的约束,并且没有指向根。

或者,树可以作为整体(全局)抽象地定义为有序树,其中值分配给每个节点。这两种观点都是有用的:虽然树可以作为一个整体进行数学分析,但实际上表示为数据结构时,它通常由节点表示和处理(而不是作为一组节点和节点之间的边缘的邻接列表),例如,可以代表一个有向图。例如,查看整个树,可以讨论给定节点的“父节点”,但通常作为数据结构,给定节点仅包含其子节点的列表,但不包含对其的引用它的父母(如果有的话)。

内容

  • 初步定义
  • 数学定义
    • 无序的树
      • 兄弟姐妹套装
      • 使用集包含
      • 有根据的树木
      • 使用递归对
    • 有序树
      • 使用水平顺序定义
      • 确定性表
      • XPath Axes
      • 遍历地图
      • 生成结构
      • 使用二叉树的定义
      • 按序列编码
      • 每级订购
  • 树木中使用的术语
    • 数据类型与数据结构
    • 递归
    • 类型理论
    • 数学的
  • 术语
  • 绘图树
  • 交涉
  • 推广
    • 有向图
  • 遍历方法
  • 常见的操作
  • 常见用途
  • 也可以看看
    • 其他树木
  • 笔记
  • 参考
  • 进一步阅读
  • 外部链接

初步定义

不是树:两个非连接部分,A→B和C→D→E。有一个以上的根。

不是树:无向循环1-2-4-3。4有多个父(入站边)。

不是树:循环B→C→E→D→B。B有多个父(入站边)。

不是树:循环A→A。A是根,但它也有父。

每个线性列表通常都是一棵树

与作为线性数据结构的数组,链表,堆栈和队列相比,树是非线性数据结构。树可以是空的,没有节点,或者树是由称为根的一个节点和零或一个或多个子树组成的结构。

数学定义

无序的树

在数学上,一个无序树[1] (或“代数树” [2] )可被定义为一个代数结构 ,其中X是的非空载波集合的节点上的函数X,其分配的每个节点X它的“ parent“node,parentx)。该结构受制于每个非空子代数必须具有相同的固定点的条件。也就是说,必须存在唯一的“根”节点r,使得r)= r对于每个节点x,一些迭代应用程序(... parentx)...))等于r

有几个等价的定义。作为最接近的替代方案,可以将无序树定义为部分代数 (X,  父代) ,其通过让r)未定义而从上述总代数中获得。也就是说,根r是唯一没有定义函数的节点,对于每个节点 x,根在有向图(X,  parent)中可以从到达。这个定义实际上与一个定义是一致的 反arborescence。该TAOCP本书使用术语面向树。[3]

另一个等价的定义是单根理论树的定义,它的高度最多为ω(有限树[4])。也就是说,代数结构(X)等效于具有顶部元素r的部分阶数 , 并且其每个主要扰乱(也称为主滤波器)是有限链。确切地说,我们应该谈论逆集理论树,因为集合论定义通常采用相反的排序。之间的对应关系 (X,  )和(X,≤)通过自反传递闭包 / 缩减建立,减少导致没有根循环的“部分”版本。

描述集理论(DST)中树的定义利用部分阶(X,≥)的表示作为有限序列之间的前缀顺序。事实证明,直到同构,DST树的(逆)和到目前为止定义的树结构之间存在一对一的对应关系。

我们可以将的四个等价表征称为 代数, 树作为部分代数, 树作为部分顺序, 树作为前缀顺序。还有第五个等价定义 - 图论的有根树,它只是一个连通的非循环有 根 图。


树的表达式(部分)代数 (X,  parent)直接遵循使用父指针的树结构的实现。通常,使用部分版本,其中根节点没有父节点定义。然而,在一些实现或模型中,甚至建立r)= r循环度。值得注意的例子:

  • Linux VFS 中“root dentry有一个指向自身的d_parent” [5]。
  • 来自面向对象编程 的实例化树 [6] [7] [8]的概念。在这种情况下,根节点是顶级元类 - 唯一的类是它自身的直接实例。

请注意,上面的定义允许无限树。这允许通过惰性评估来描述由一些实现支持的无限结构。一个值得注意的例子是无限倒退的eigenclasses从Ruby的对象模型。[9] 在这个模型中,通过superclass非终端对象之间的链接建立的树是无限的,并且具有无限分支(“螺旋”对象的单个无限分支 - 参见图)。

兄弟姐妹套装

在每个无序树(X)中,存在节点集X兄弟集的区别分区。如果parentx)=  parenty),则两个非根节点xy属于同一兄弟集 。根节点r形成单子兄弟集{ r }。[a] 如果一棵树的每个兄弟集是有限的,那么它就被称为局部有限有限分支

每对独特的兄弟姐妹是无可比拟在≤。这就是定义中使用无序字的原因。当所有兄弟集合都是单例时,即当所有节点的集合完全有序(并因此有序排列)≤时,这样的术语可能会产生误导。在这种情况下,我们可能会谈论一个单分支树

使用集包含

与每个部分有序集合一样,树结构(X,≤)可以用包含顺序表示- 通过 集合系统,其中≤与⊆重合,即诱导包含顺序。考虑一个结构(U,ℱ),使得U是非空集,并且ℱ是U的一组子集,使得满足以下条件:

  1. ∅ &NotElementℱ。(也就是说,(U,ℱ)是一个超图。)
  2. ü ∈ℱ。
  3. 对于每一个Xÿ从ℱ,   X ∩ ÿ ∈{∅,XY ^ }。(也就是说,ℱ是一个层流家庭。[10])
  4. 对于每一个X从ℱ,只有有限个 Ÿ从ℱ使得 X ⊆ ÿ

则结构(ℱ,⊆)是一个无序树,其根等于ü。相反,如果(U,≤)是无序树,则ℱ是集合{↓ x | X ∈ ü } 所有的主理想的(Ú,≤) 然后一组系统(Û,ℱ)满足上述性质。

 

树作为层的系统集(从嵌套集模型复制)

树结构的集合系统视图提供默认语义模型 - 在大多数最常见的情况下,树数据结构表示包含层次结构。这也为订单方向提供了理由,其根位于顶部:根节点是比任何其他节点更大的容器。值得注意的例子:

  • 文件系统的目录结构。目录包含其子目录。
  • DOM树。与DOM节点相对应的文档部分根据树顺序处于子部分关系。
  • 面向对象编程中的单继承。类的实例也是超类的实例。
  • 分层分类法,如杜威十进制分类,具有越来越具体的部分。
  • BSP树木,四叉树,八叉树,R树和用于递归其它树数据结构空间分割。

有根据的树木

如果严格的偏序<是一个有根据的关系,那么无序树(X,≤)是根据的。特别是,每个有限的树都是有根据的。假设依赖选择的公理,当且仅当它没有无限分支时,树才是有根据的。

有根据的树木可以递归地定义 - 通过从较小的树木的不相交的联合形成树木。对于精确定义,假设X是一组节点。使用偏序的反身性,我们可以识别X子集上的任何树(Y,≤),其部分顺序(≤) - X  ×  X的子集。集合ℛ一切关系[R形成一个有根有据的树(Ÿ,  [R )上的一个子集ŸX分阶段定义ℛ我,使得 ℛ=⋃{ℛ  | 是顺序的 }。对于每个序数 ,让 - [R属于个级 ℛ 当且仅当 [R等于

⋃ ℱ ∪((DOM(⋃ ℱ)∪{ X })×{ X })

其中ℱ的一个子集⋃{ℛ ķ | ķ <  },使得元件ℱ是两两相交,并且X是不属于一个节点的DOM(⋃ ℱ)。(我们使用DOM(小号)来表示域的关系的小号。)观察到最低的阶段ℛ 0由单节点树的{(XX)},因为只有空ℱ是可能的。在每个阶段,(可能) 通过采取森林建立新的树木R.⋃ ℱ与部件ℱ从下段和附加一个新的根X顶上⋃ ℱ。

与最高ω 的树相比,有根据的树木的等级是无限的。[11]

使用递归对

在计算中,定义有根据的树的常用方法是通过递归有序对 (Fx):树是森林F和新节点x。[12] 阿森林 ˚F又是一个可能是空的组具有成对不相交的集合的节点的树。对于精确定义,与强制集合论技术中使用的名称结构类似地进行。设X是一组节点。在上层建筑过X,定义套Ť,ℱ的分别树木和森林,和地图节点  :Ť →℘(X) 分配每个树其底层节点集合,使得

X上的树木)    ∈ Ť  ↔  是一对(˚FX)从ℱ × X使得对于所有š ∈ ˚F

X ∉ 节点小号),
X上的森林)   ˚F ∈ℱ  ↔  ˚F 的一个子集Ť使得对于每š ∈ ˚F小号 ≠ 

节点小号)∩ 节点)=∅,
(树木的节点)   Ý ∈ 节点  ↔     =(˚FX)∈ Ť

ÿ = XÝ ∈ 节点小号)的一些š ∈ ˚F 。

通过将T,ℱ和节点中的每一个分层为如前一小节中的阶段,可以消除上述条件中的圆形。随后,定义一个子树关系≤上Ť作为的自反传递闭包直接子树关系≺由树木之间限定

小号 ≺ Ť   ↔   小号&Element; π 1(

其中π 1()是突起的到第一坐标,即,它是森林˚F使得 =(˚FX)为一些X ∈ X。可以观察到,(Ť,≤)是一个多树:每 ∈ Ť,委托人理想↓ 由有序≤是有充分理由的树作为部分顺序。此外,每棵树牛逼 ∈ ŧ,其节点阶结构(节点),≤ )由下式给出X ≤  ÿ当且仅当有森林˚Fģ ∈ℱ,使得两个(˚FX)和(g ^ÿ)是子树的和(˚FX)≤(G ^ÿ)。

有序树

前一小节中介绍的结构只是计算中出现的树数据结构的核心“分层”部分。在大多数情况下,兄弟姐妹之间还有一个额外的“横向”排序。在搜索树中,顺序通常由与每个兄弟相关联的“密钥”或值建立,但在许多树中并非如此。例如,XML文档,JSON文件中的列表以及许多其他结构的顺序不依赖于节点中的值,而是本身的数据 - 按字母顺序对小说的段落进行排序会丢失信息。[ 可疑 ]

可以通过如下给每个兄弟集合赋予线性顺序来定义先前描述的树结构(X,≤)的对应扩展。[13] [14] 根据Kuboyama [1 ]的另一种定义将在下一小节中介绍。

一个有序的树是一个结构 (X,≤ V,≤ 小号) ,其中 X是一个非空组节点和 ≤ V 和 ≤ 小号 上关系X 称为v ertical(或也分层[1] )顺序和 小号 ibling订单,分别。结构符合以下条件:

  1. X,≤ V) 是,其是如前一小节所定义的无序树的部分顺序。
  2. X,≤ 小号) 是一个偏序。
  3. 不同节点在可比< 小号当且仅当他们是兄弟: (< 小号)  ∪  (> 小号)=((≺ V)  ○  (≻ V))  ∖  ID X

(在有限树的情况下,可以省略以下条件。)

  1. 每个节点具有唯一的有限多个前述兄弟姐妹,即的每主理想(X,≤ 小号)是有限的。

条件(2)和(3)说(X,≤ 小号) 是一个逐个分量线性顺序,每个部件是一个兄弟集。条件(4)声称,如果同级设置小号是无限然后(小号,≤ 小号)是同构 (ℕ,≤) ,自然数的通常顺序。

鉴于此,有三个(另一个)区分的部分顺序,由以下处方唯一给出:

(< H)  =  (≤ V)  ○  (< 小号)  ○  (≥ V)   (h orizo​​ntal order),
(< L -)  =  (> V)  ∪  (< H)   (所述“不和谐”  inear顺序),
(< L⁺)  =  (< V)  ∪  (< H)   (所述“一致”  inear顺序)。

这相当于一个“VSHL ±五个偏序”系统 ≤ V, ≤ 小号, ≤ ħ, ≤ L⁺, ≤ L⁻ 在同一组X的节点,其中,除了一对 {≤ 小号,≤ ħ },任何两个关系唯一地确定其他三个,参见确定性表。

关于符号约定的注释:

  • 本小节中使用 的关系组成符号○将从左向右(as )解释。 
  • 符号<和≤表示部分订单的严格非严格版本。
  • 符号>和≥表示相反的关系。
  • 所述≺符号被用于 覆盖关系的≤这是即时的部分顺序的版本。

这产生六个版本 ≺, <,≤, ≻, >,≥对于单个部分顺序关系。除了 ≺ 和 ≻,每个版本唯一地确定了别人。从≺传递到< 要求<可传递性地减少。对于< V, < S和 < H的所有值总是满足, 但如果X是无穷大,则可能不能保持 < L⁺或 < L -。


部分订单 ≤ V和 ≤ ħ 是互补的: (< V) ⊎ (> V) ⊎ (< ħ) ⊎ (> ħ)=  X × ∖ ID X。其结果是,该“一致”线性顺序 < L⁺ 是一个线性延伸的 < V。类似地,< L - 是> V的线性延伸 。

覆盖关系 ≺ L⁻和 ≺ L⁺对应于前序遍历和 后序遍历,分别。如果X ≺ L⁻ ÿ 然后,根据是否ÿ具有上一个兄弟与否,X节点要么的前一个兄弟的“最右边的”非严格后裔Ý,或者在后一种情况下,X是第一子的ÿ。对后一种情况的形成的关系 (≺ L⁻) ∖ (< ħ) 这是一个部分映射,为每个非叶节点分配其第一个子节点。同样, (≻ L⁺)∖(> ^ h) 指定有穷个孩子每个非叶节点的最后一个子节点。

使用水平顺序定义

该久保山的“扎根有序树”的定义[1]利用水平秩序≤ ^ h作为definitory关系。[b] (另见Suppes。[15])使用到目前为止引入的符号和术语,定义可以表示如下。

一个有序的树是一个结构 (X,≤ V,≤ ħ),使得条件(1-5)被满足:

  1. X,≤ V) 是为一个局部顺序无序树。(该v ertical顺序。)
  2. X,≤ ħ) 是一个偏序。(h orizo​​ntal order。)
  3. 部分订单 ≤ V和 ≤ ħ 是互补的: (< V) ⊎ (> V) ⊎ (< ħ) ⊎ (> ħ)=  X × ∖ ID X。 

    (即,在节点的对无可比拟 中(< V) 是在可比较的(< ħ),反之亦然。)
  4. 部分订单 ≤ V和 ≤ ħ 是“一致的”: (< ħ)=(≤ V) ○ (< ħ) ○ (≥ V)。 

    (也就是说,对于每一个节点Xÿ使得 X < ħ ÿ,的所有后代X必须先于的所有后代ÿ)。

(与之前一样,在有限树的情况下可以省略以下条件。)

  1. 每个节点只有很多前面的兄弟节点。(也就是说,对于每一个无限兄弟设置 小号,(小号,≤ ħ)具有顺序型的自然数。)

同级次序(≤ 小号)通过以下步骤获得 (< 小号)=(< ħ) ∩ ((≺ V) ○ (≻ V)) ,即,两个不同的节点是在同级次序当且仅当它们在水平顺序和是兄弟姐妹。

确定性表

下表显示了“VSHL ± ”系统的确定性。根据列,表体中的关系表达式等于 < V, < S, < H, < L - 或 < L⁺之一 。由此可见,除了一对{≤ 小号,≤ ħ  },有序树(X,...)被唯一地由五个关系中的任何两个来确定。

  < V < S < H < L - < L⁺
V,S     (≤ V)○(< 小号)○(≥ V)    
V,H   (< ħ)∩((≺ V)○(≻ V))   (> V)∪(< H) (< V)∪(< H)
V,L⁻   (< L⁻)∩((≺ V)○(≻ V)) (< L -)∖(> V)    
V,L⁺   (< L⁺)∩((≺ V)○(≻ V)) (< L⁺)∖(< V)    
H,L⁻ (> L -)∖(< H)        
H,L⁺ (< L⁺)∖(< H)        
L⁻,L⁺ (> L -)∩(< L⁺)   (< L -)∩(< L⁺)    
S,L⁻ X ≺ V Ŷ ↔ Ŷ = INF L⁻(Ý) 其中 ÿ是的图像 { X }下(≥ 小号)○(≻ L⁻)
S,L⁺ X ≺ V Ŷ ↔ Ŷ = SUP L⁺(Ý) 其中 ÿ是的图像 { X }下(≤ 小号)○(≺ L⁺)

在最后两行, INF L⁻(Ý)表示下确界的Ý在 (X,≤ L⁻),和 SUP L⁺(Ý)表示确的Ý在 (X,≤ L⁺)。在两行,(≤ 小号)RESP。(≥ 小号)可以用等价的兄弟替换等价 (≤ 小号)○(≥ 小号)。特别地,分割成兄弟设置与任一一起 ≤ L⁻或 ≤ L⁺ 也足以确定有序的树。用于第一处方≺ V可以理解为:一个非根节点的父X等于设定的兄弟姐妹的所有直系前辈的下确界X,其中词语下确界和前辈意WRT ≤ L⁻。类似地,对于第二个药方,只是使用确界,继任者和 ≤ L⁺。

该关系≤ 小号和 ≤ ^ h显然不能形成definitory对。对于最简单的示例,考虑一个有两个节点的有序树 - 然后无法分辨哪个是根。

XPath Axes

XPath Axis 关系
ancestor < V
ancestor-or-self ≤ V
child ≻ V
descendant > V.
descendant-or-self ≥ V
following < H
following-sibling < S
parent ≺ V
preceding > H.
preceding-sibling > S.
self id X

右边的表格显示了引入的关系与XPath轴的对应关系,XPath轴在结构化文档系统中用于访问与起始“上下文”节点具有特定排序关系的节点。对于上下文节点[16] x,由左列中的说明符命名的是在对应关系下等于{ x }的图像的节点集 。从XPath 2.0开始,节点按文档顺序 “返回” ,这是“不一致”线性顺序 ≤L -。将实现“一致性”,V被相反地定义,其中自下而上的方向与根据自然树的集合理论一样向外。[C]

遍历地图

下面是通常用于有序树遍历的部分映射列表。[17] 每个地图是一个杰出的功能的subrelation ≤ L⁻或其相反的。

  • ≺ V ...的父节点的部分地图,
  • ≻ Š ......在以前的同胞部分地图,
  • ≺ Š ...的下一个兄弟部分地图,
  • (≺ L⁻)∖(< ħ) ...的第一子部分地图,
  • (≻ L⁺)∖(> ^ h) ......在最后一子部分地图,
  • ≻L - ......前一节点部分地图,
  • ≺ L⁻ ...的下一个节点部分地图。

生成结构

遍历映射构成了部分一元代数[18] (XparentpreviousSibling,...,nextNode) ,它们构成了将树表示为链接数据结构的基础。至少在概念上,存在父链接,兄弟邻接链接以及第一个/最后一个子链接。这也适用于一般的无序树,可以在Linux VFS中的dentry数据结构中观察到。[19]

与部分顺序的“VSHL ± ”系统类似,存在唯一确定整个有序树结构的遍历映射对。自然地,一个这样的产生结构是 (X, ≺ V, ≺ 小号) 可转录为(XnextSibling) -的亲代和下一个同胞链接的结构。另一个重要的生成结构是 (XfirstChildnextSibling),称为左子右兄弟二叉树。该部分代数在二叉树和有序树之间建立一对一的对应关系。

使用二叉树的定义

与二叉树的对应关系提供了有序树作为部分代数的简明定义。

一个有序的树是一个结构,其中 X是一个非空组节点,和 LCRS是局部地图上X称为  eft- Ç希尔德和 - [R ight- 小号 ibling分别。结构符合以下条件: 

  1. 部分映射lcrs是不相交的,即 (lc)  ∩  (rs)=  ∅ 。
  2. 的倒数(LC)  ∪  (RS)是一个局部地图p ,使得局部代数(Xp)是一个无序树。

的偏序结构(X,≤ V,≤ 小号) 如下获得:

(≺ 小号)  =  rs),
(≻ V)  =  LC)  ○  (≤ 小号)。

按序列编码

有序树可以由有限的自然数序列自然编码。[20] [d] 表示ω⁎所有有限自然数序列的集合。然后,任何非空子集w ^ ω的⁎所采取下关闭前缀产生了一个有序的树:只取前缀为了≥ V 和字典顺序为 ≤ L⁻。相反地,对于一个有序树Ť =(X,≤ V,≤ L⁻) 分配的每个节点X的序列兄弟索引,即根被分配空序列,并且对于每个非根节点x,令wx)= wx))⁎[ i ]其中i是前面的兄弟姐妹的数量x 和⁎是连接运算符。把W = { wx)| X ∈ X }。然后w ^,配备有感应订单 ≤ V(前缀次序的倒数)和 ≤ L⁻(字典顺序),与T同构。

每级订购

 

虚线表示< B -排序(从树遍历复制)

作为“VSHL ± ”系统的可能扩展,可以基于树的级别结构来定义节点之间的另一个区别关系。首先,让我们用~ E表示x ~ E y定义的等价关系, 当且仅当 xy具有相同数量的祖先时。这产生了节点集合的分区为级别 0,1,...(,n) - 分区到兄弟集合的粗略化。然后定义关系 <ê, < B⁻ 和 < B⁺ 由

 

可以观察到< E 是严格的偏序, < B - 和 < B⁺是严格的总阶数。此外,“VSL ± ”和“VEB ± ”系统之间存在相似性: < E 是分量线性的并且正交于 < V, < B - 是< E 和> V的线性延伸 ,并且 < B⁺ 是< E 和< V的线性扩展 。

树木中使用的术语

节点

节点是可以包含值或条件的结构,或者表示单独的数据结构。

树中的顶级节点,主要祖先。

儿童

远离根时直接连接到另一个节点的节点,即直接后代。

一个孩子的相反概念,一个直接的祖先。

兄弟姐妹

一组具有相同父节点的节点。

邻居

父母或子女。

后裔

通过从父节点到子节点重复进行的节点。也称为子孙

祖先

通过从子节点到父节点重复进行的节点。

叶/外部节点(不常见)

没有子节点的节点。

分支节点/内部节点

具有至少一个孩子的节点。

学位

对于给定节点,其子节点数。叶子必须是零度。树的程度是其根的程度。

树的程度

根的程度。

边缘

一个节点与另一个节点之间的连接。

路径

连接节点和后代的节点和边的序列。

距离

沿两个节点之间最短路径的边数。

深度

节点与根之间的距离。

水平

1  +节点与根之间的边数,即(深度+ 1)

高度

节点和后代叶之间最长路径上的边数。

宽度

级别中的节点数。

宽度

叶子的数量。

树的高度

根节点的高度或树中任何节点的最大级别

森林

一组Ñ ≥0不相交树。

子树

Ť是由在一个节点的树Ť及其所有在后代Ť

有序树

一个有根树,其中为每个顶点的子节点指定了一个排序。

树的大小

树中的节点数。

数据类型与数据结构

树作为抽象数据类型和具体数据结构之间存在区别,类似于列表和链表之间的区别。作为一种数据类型,树有一个值和孩子,孩子们自己就是树; 树的值和子节点被解释为根节点的值和根节点的子节点的子树。要允许有限树,必须允许子列表为空(在这种情况下,树可以要求非空,“空树”而不是由零树林表示),或允许树到是空的,在这种情况下,如果需要,子列表可以是固定大小(分支因子,尤其是2或“二进制”)。

作为数据结构,链接树是一组节点,其中每个节点都有一个值和一个对其他节点(其子节点)的引用列表。还要求没有两个“向下”引用指向同一节点。实际上,树中的节点通常也包括其他数据,例如下一个/先前的引用,对其父节点的引用,或几乎任何内容。

由于在链接树数据结构中使用对树的引用,因此通常会隐式地讨论树,假设它们通过对根节点的引用来表示,因为这通常是它们实际实现的方式。例如,可以使用空引用而不是空树:树总是非空的,但对树的引用可以为空。

递归

递归地,作为数据类型,树被定义为值(某些数据类型,可能是空的),以及树的列表(可能是空列表),其子树的子树; 象征:

t:v [t [1],...,t [k]]

(树t由值v和其他树的列表组成。)

更优雅的是,通过相互递归,其中树是最基本的示例之一,树可以用森林(树木列表)来定义,其中树由值和森林组成(其子树)孩子):

f:[t [1],...,t [k]]
t:vf

请注意,此定义是以值为单位的,并且适用于函数式语言(它假定参考透明度); 不同的树没有联系,因为它们只是值列表。

作为一个数据结构,树被定义为一个节点(根),它本身由一个值(某些数据类型,可能是空)组成,还有一个对其他节点的引用列表(列表可能为空,引用可能为null) ); 象征:

n:v [&n [1],...,&n [k]]

(节点n由值v和其他节点的引用列表组成。)

这个数据结构定义了一个有向图,[e]并且它是一个树,必须在它的全局结构(它的拓扑结构)上添加一个条件,即最多一个引用可以指向任何给定的节点(一个节点最多单个父级),树中没有节点指向根。实际上,每个节点(除根之外)必须只有一个父节点,并且根节点必须没有父节点。

实际上,给定一个节点列表,并且对于每个节点一个对其子节点的引用列表,如果没有分析其全局结构,并且它实际上在拓扑上是一棵树,则无法判断该结构是否是树,如下所述。

类型理论

作为ADT,使用抽象林类型F(树的列表),通过函数 定义具有某种类型E的值的抽象树类型T.

值:T → E

孩子:T → F

零:()→ F

节点:E × F → T

用公理:

value(node(ef))= e

children(node(ef))= f

就类型理论而言,树是由构造函数nil(空林)和节点(具有给定值和子节点的根节点的树)定义的归纳类型。

数学的

从整体上看,树数据结构是有序树,通常具有附加到每个节点的值。具体而言,它是(如果需要非空):

  • 带有“远离根”方向的根树(更狭窄的术语是“ 树状 ”),意思是:
    • 有向图,
    • 其基本的无向图是一棵树(任意两个顶点只通过一条简单的路径连接),
    • 具有明显的根(一个顶点被指定为根),
    • 它确定边缘上的方向(箭头指向远离根部;给定边缘,边缘指向的节点称为父节点,边缘指向的节点称为子节点),

和...一起:

  • 给定节点的子节点上的排序,和
  • 每个节点的值(某些数据类型)。

树通常具有固定的(更恰当的,有界的)分支因子(outdegree),特别是总是具有两个子节点(可能是空的,因此最多两个非空的子节点),因此是“二叉树”。

允许空树使一些定义更简单,更复杂:根树必须是非空的,因此如果允许空树,则上述定义变为“空树或根树,以便......”。另一方面,空树简化了定义固定分支因子:允许空树,二叉树是树,每个节点都有两个子节点,每个子节点都是树(可能是空的)。树上的完整操作集必须包括fork操作。

术语

节点为可以含有一个值或条件,或者代表一个单独的数据结构(它可以是它自己的树)的结构。树中的每个节点都有零个或多个子节点,它们位于树下面(按照惯例,树木会向下绘制)。具有子节点的节点称为子节点父节点(或祖先节点或上级节点)。一个节点最多只有一个父节点。

一个内部节点(也被称为内节点索引节点的简称,或者分支节点)是具有子节点树的任意节点。类似地,外部节点(也称为外部节点叶节点终端节点)是没有子节点的任何节点。

树中最顶层的节点称为根节点。根据定义,可能需要树具有根节点(在这种情况下所有树都是非空的),或者可以允许树为空,在这种情况下,它不一定具有根节点。作为最顶层的节点,根节点将不具有父节点。它是树上算法开始的节点,因为作为数据结构,人们只能从父母传递给孩子。注意,一些算法(例如后序深度优先搜索)从根开始,但首先访问叶节点(访问叶节点的值),只访问最后一根(即,他们首先访问根的子节点) ,但只能访问最后的根值)。可以通过跟随链接从其到达所有其他节点。(在正式定义中,每个这样的路径也是唯一的。)在图中,根节点通常在顶部绘制。在某些树中,例如堆,根节点具有特殊属性。树中的每个节点都可以看作以该节点为根的子树的根节点。

节点的高度是从该节点到叶子的最长向下路径的长度。根的高度是树的高度。节点的深度是到其根的路径的长度(即,其根路径)。在操纵各种自平衡树,特别是AVL树时,通常需要这样做。根节点具有深度零,叶节点具有高度零,并且仅具有单个节点的树(因此根和叶)具有深度和高度零。通常,空树(没有节点的树,如果允许的话)具有高度-1。

子树Ť是由在一个节点的树Ť及其所有在后代Ť。[f] [21]节点因此对应于子树(每个节点对应于其自身及其所有后代的子树) - 对应于根节点的子树是整个树,并且每个节点是它确定的子树的根节点; 对应于任何其他节点的子树称为适当的子树(类似于适当的子集)。

绘图树

树木经常被绘制在飞机上。有序树可以在平面中基本上唯一地表示,因此被称为平面树,如下所示:如果一个修复常规顺序(例如,逆时针),并按顺序排列子节点(第一个进入的父边缘,然后是第一个子节点)边缘等),这产生了树在平面中的嵌入,独特于环境同位素。相反,这种嵌入确定了子节点的排序。

如果将根置于顶部(父节点位于子节点之上,如在族树中)并将所有节点放置在距给根的给定距离(根据边数:树的“级别”)的给定位置上水平线,一个获得树的标准图。给定二叉树,第一个子项位于左侧(“左侧节点”),第二个子项位于右侧(“右侧节点”)。

交涉

有许多不同的方式来表示树木; 公共表示将节点表示为动态分配的记录,其中包含指向其子节点,父节点或两者的指针,或者作为数组中的项目,它们之间的关系由它们在数组中的位置(例如,二进制堆)确定。

实际上,二叉树可以实现为列表列表(列表中的值是列表):列表的头部(第一项的值)是左子(子树),而尾部(列表)第二个及后续术语)是正确的孩子(子树)。这可以修改为允许值,如在Lisp S表达式中,其中head(第一项的值)是节点的值,尾部的头部(第二项的值)是左子项,并且尾部的尾部(第三个和后续术语的列表)是正确的孩子。

通常,树中的节点不会有指向其父节点的指针,但可以包含此信息(将数据结构扩展为还包括指向父节点的指针)或单独存储。或者,向上链接可以包括在子节点数据中,如在线程二叉树中。

推广

有向图

如果将边(到子节点)视为引用,则树是有向图的特例,并且树数据结构可以通过删除节点最多只有一个父节点的约束来推广以表示有向图,并且不允许循环。边缘仍被抽象地视为节点对,但是,父节点和子节点通常被不同的术语(例如,源和目标)替换。不同的实施策略存在:有向图可以用与树(具有值和子列表的节点)相同的本地数据结构来表示,假设“子列表”是引用列表,或者由诸如邻接列表之类的结构全局地表示。

在图论中,树是连通的非循环图 ; 除非另有说明,否则在图论中,假定树和图是无向的。这些树和树之间没有一对一的对应关系作为数据结构。我们可以采用一个任意的无向树,任意选择其中一个顶点作为根,通过使它们指向远离根节点的方式使其所有边缘定向 - 产生一个树枝 - 并为所有节点分配一个顺序。结果对应于树数据结构。选择不同的根或不同的顺序会产生不同的顺序。

给定树中的节点,其子节点定义有序林(由所有子节点给出的子树的并集,或等效地获取节点本身给出的子树并擦除根)。就像子树对于递归一样自然(如在深度优先搜索中),森林对于核心运动是很自然的(如在广度优先搜索中)。

通过相互递归,可以将林定义为树的列表(由根节点表示),其中(树的)节点由值和林(其子节点)组成:

f:[n [1],...,n [k]]
n:vf

遍历方法

主要文章:树遍历

通过父母和孩子之间的联系,逐步穿过树的物品,称为走树,行动是的“ 行走 ”。通常,当指针到达特定节点时可以执行操作。在其子节点被称为预订步行之前遍历每个父节点的步行; 孩子们在他们各自的父母被遍历之前经过的步行称为后序步行; 一个节点的左子树,然后节点本身,最后遍历其右子树的遍历被称为有序遍历。(最后一个场景,正好指两个子树,一个左子树和一个右子树,具体假设一个二叉树。)水平顺序步行有效地在整个树上执行广度优先搜索 ; 逐级遍历节点,首先访问根节点,然后是其直接子节点及其兄弟节点,然后是其孙节点及其兄弟节点等,直到遍历了树中的所有节点。

常见的操作

  • 枚举所有项目
  • 枚举树的一部分
  • 正在搜索一个项目
  • 在树上的某个位置添加新项
  • 删除项目
  • 修剪:删除树的整个部分
  • 嫁接:将整个部分添加到树中
  • 查找任何节点的根目录
  • 找到两个节点的最低共同祖先

常见用途

  • 表示分层数据,例如语法树
  • 以有效搜索的方式存储数据(请参阅二叉搜索树和树遍历)
  • 表示已排序的数据列表
  • 作为合成视觉效果的数字图像的工作流程[ 需要引证 ]
  • 存储Barnes-Hut树用于模拟星系
class Node:
    def __init__(self,item):
        self.item = item
        self.child1 = None
        self.child2 = None


class Tree:
    def __init__(self):
        self.root = None

    def add(self, item):
        node = Node(item)
        if self.root is None:
            self.root = node
        else:
            q = [self.root]

            while True:
                pop_node = q.pop(0)
                if pop_node.child1 is None:
                    pop_node.child1 = node
                    return
                elif pop_node.child2 is None:
                    pop_node.child2 = node
                    return
                else:
                    q.append(pop_node.child1)
                    q.append(pop_node.child2)

    def traverse(self):  # 层次遍历
        if self.root is None:
            return None
        q = [self.root]
        res = [self.root.item]
        while q != []:
            pop_node = q.pop(0)
            if pop_node.child1 is not None:
                q.append(pop_node.child1)
                res.append(pop_node.child1.item)
            if pop_node.child2 is not None:
                q.append(pop_node.child2)
                res.append(pop_node.child2.item)
        return res
    def preorder(self,root): # 先序遍历
        if root is None:
            return []
        result = [root.item]
        left_item = self.preorder(root.child1)
        right_item = self.preorder(root.child2)
        return result + left_item + right_item

    def inorder(self,root):  # 中序序遍历
        if root is None:
            return []
        result = [root.item]
        left_item = self.inorder(root.child1)
        right_item = self.inorder(root.child2)
        return left_item + result + right_item

    def postorder(self,root):  # 后序遍历
        if root is None:
            return []
        result = [root.item]
        left_item = self.postorder(root.child1)
        right_item = self.postorder(root.child2)
        return left_item + right_item + result

t = Tree()
for i in range(10):
    t.add(i)
print('层序遍历:',t.traverse())
print('先序遍历:',t.preorder(t.root))
print('中序遍历:',t.inorder(t.root))
print('后序遍历:',t.postorder(t.root))

 

你可能感兴趣的:(基础知识)