美文网首页
数据结构学习笔记——第五章:树

数据结构学习笔记——第五章:树

作者: 吃远 | 来源:发表于2019-07-24 15:16 被阅读0次

一、动机

  我们之前已经学习了几种常用的线性数据结构,为什么还要引入树结构?

  回顾一下之前学习的数据结构,无论是vector和stack还是list与queue,其中涉及的操作无非是静态操作(如查找)和动态操作(插入、删除等)两种。而对于向量、列表两大线性结构而言,这两种操作各有一种复杂度相对较大。如下图:


树结构结合了列表和向量的优点 树相关描述指标:高度与深度 树的表示——树应该提供的一些接口
父节点表示法的不足——需要改进访问子节点/兄弟节点的复杂度 子节点表示法: 解决了向下查找的问题,但是向上查找复杂度较高 结合父子节点表示法——存储小数据集需要额外O(n)空间
  • 父+子表示法美中不足的地方在于,每个节点要存储所有子节点的索引。首先这样需要花费额外的O(n)的空间复杂度,其次各个节点的子数据集长度相差悬殊,显得并不规范。
长子+兄弟表示法
  • 每个节点只存储其长子和兄弟两个引用。这样每个节点只需要常数空间,且长度彼此接近。
  • 后面将要学习的二叉树的很多实现就是基于长子+兄弟表示法。

二、二叉树

  节点度数不超过2的树称为二叉树。对于深度为h的二叉树,节点个数有如下关系: h<n<2^{h+1}

  对于二叉树而言,其高度增长缓慢,宽度增长迅速。

2.1 真二叉树
真二叉树

  真二叉树即所有节点的出度均为偶数的二叉树。在实际使用中我们可以将出度不为2的节点通过虚拟的“补0”操作来补充成真二叉树。可以证明这种补充在渐进意义上并不会增加复杂度,但是对于后续的算法会提供很大的便利。

2.2 多叉树

  凡是有根且有序的树,都可以用二叉树完全描述。


将普通的树通过长子+兄弟表示法转换为二叉树
  • 故接下来研究和实现树相关算法时,都可以仅对二叉树进行研究和实现。
2.3 遍历二叉树
二叉树的几种遍历方法
2.3.1 先序遍历
  • 递归实现:


    先序遍历的递归实现

  既然递归实现的复杂度就是O(n),是不是就已经是最好的算法了呢?这里涉及两个概念:

①“每帧”的复杂度:
  假如每个递归实例对应于一帧,但是因为递归要求的通用格式,所以每帧尽管对应常数复杂度,但是这里的常数却不一定足够小。

②尾递归
  递归调用出现在整个递归实例体的尾部。这种递归很容易转换为迭代,比如引入一个栈。

  此外,迭代实现有助于更清晰地了解整个流程。


先序遍历迭代版本

  注意左右孩子的入栈次序。为了先遍历左子树再遍历右子树,我们需要先将右子树压入栈中。

实例分析

  先序遍历的迭代版本一看起来非常自然,同时清晰明了。但是这种实现方法很难推广到后面的中序遍历和后序遍历。下面可以尝试一种更通用的思路。

  • 左侧链
    迭代二版本:利用左侧链展开算法
      思路:将二叉树每个局部区域视为一条左侧链,链条上的节点又可以延伸出一个右子树。在遍历时先自顶向下遍历左侧链,然后自底向上逐一访问右子树即可。
脉络 迭代2实现

  每沿着左侧链向下一个节点,首先对该节点数据进行访问,然后将该节点的右孩子压入栈中。当当前左侧链访问到最后时(最后一个节点的左子树为空),退出当前左侧链,弹出栈顶的子树根节点,在对新的节点遍历左侧链。

迭代2朱算法
2.3.2 中序遍历
  • 还是从递归算法开始:


    递归算法
观察 中序遍历思路

  遍历思路:
①从根节点开始,沿着左侧链顺序而下,将遇到的每个左子树节点压入栈中;
②遍历完当前左侧链(最后一个节点的左子树为空),随即退出,弹出栈顶元素
③对弹出的栈顶元素进行访问
④以刚访问过的节点为起点,遍历左侧链。

实现 实例分析
2.3.3 层次遍历

若队列不为空,则继续:
①每次循环开始,去除队首的元素进行访问;
②“左顾右盼”,将左子树和右子树加入(如果有的话)队列;

层次遍历实现 层次遍历实例

2.4 树重构

  我们知道,给定一棵二叉树,我们可以完全地导出其先序遍历序列、中序遍历序列以及后续遍历序列。那反过来,能否根据这三个序列导出树的拓扑结构呢?

  答案是可以的。我们需要先序遍历序列或者后序遍历序列中的一个 + 中序遍历序列,就可以重构出整棵树的拓扑结构。


重构树

相关文章

网友评论

      本文标题:数据结构学习笔记——第五章:树

      本文链接:https://www.haomeiwen.com/subject/oejmlctx.html