Catalogue   

定义


树是由n(n>=1)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:

  • 每个节点有零个或多个子节点;
  • 没有父节点的节点称为根节点;
  • 每一个非根节点有且只有一个父节点;
  • 除了根节点外,每个子节点可以分为多个不相交的子树;

树的术语


  • 节点的度:一个节点含有的子树的个数称为该节点的度;
  • 树的度:一棵树中,最大的节点的度称为树的度;
  • 叶节点或终端节点:度为零的节点;
  • 父亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点;
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  • 树的高度或深度:树中节点的最大层次;
  • 堂兄弟节点:父节点在同一层的节点互为堂兄弟;
  • 节点的祖先:从根到该节点所经分支上的所有节点;
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
  • 森林:由m(m ≥ 0)棵互不相交的树的集合称为森林;

树的种类


  • 无序树:

    树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树

  • 有序树

    树中任意节点的子节点之间有顺序关系,这种树称为有序树

  • 二叉树:每个节点最多含有两个子树的树称为二叉树

    • 完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树,其中满二叉树的定义是所有叶节点都在最底层的完全二叉树;
    • 平衡二叉树:当且仅当任何节点的两棵子树的高度差不大于1的二叉树;
    • 排序二叉树:
  • 霍夫曼树(用于信息编码):带权路径最短的二叉树称为哈夫曼树或最优二叉树;

  • B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多余两个子树。

树的性质

  1. 树中的节点数等于所有节点的度数加1。
  2. 度为k的树中第i层上至多有ki-1个节点(i ≥ 1)。
  3. 深度为h的k叉树至多有个节点 $\frac{k^{h}-1}{h-1}$ 个节点。
  4. 具有n个节点的k叉树的最小深度为 ⌈$\log_{k} (n(k-1)+1)$⌉

二叉树


二叉树(Binary Tree)是指树的度为2的有序树。每个节点的左子树的根节点称为左孩子(left child),右子树的根节点称为右孩子(right child)。

性质


  1. 二叉树上终端节点数等于双分支节点数加1。
  2. 二叉树上第i层至多有2i-1个节点(i ≥ 1)。
  3. 深度为h的二叉树至多有2h-1个节点。
  4. 完全二叉树中编号为i的节点(1 ≤ i ≤ n, n ≥ 1,n为节点数 )有:
    1. 若i ≤ ⌊n/2⌋,即2i ≤ n,则编号为i的节点为分支节点,否则为叶子节点。
    2. 若n为奇数,则每个分支节点都既有左孩子,又有右孩子;若n为偶数,则编号最大的分支节点(编号为n/2)只有左孩子,没有右孩子,其余分支节点左、右孩子都有。
    3. 若编号为i的节点右左孩子,则左孩子节点的编号为2i;若编号为i的节点有右孩子,则右孩子节点的编号为2i+1。
    4. 除树根节点外,若一个节点的编号为i,则他的双亲节点的编号为 ⌊i/2⌋,也就是说,当i为偶数时,其双亲节点的编号为i/2,它时双亲节点的左孩子,
      当i为奇数时,其双亲节点的编号为(i-1)/2,它是双亲节点的右孩子。
  5. 具有n个(n > 0)节点的完全二叉树的深度为⌈$\log_{2} (n+1)$⌉或⌊$\log_{2} n$⌋+1。

二叉树的存储结构


二叉树的遍历


遍历二叉树的问题可以分为:

  • 访问根节点
  • 遍历左子树
  • 遍历右子树

遍历方式分为:DLR、LDR、LRD、DRL、RDL、RLD。

前序遍历算法

中序遍历算法

后续遍历算法

线索二叉树

二叉树的线索化

对二叉树进行某种遍历得到的节点序列,可以看做一个线性表,在该线性表中,除第一个节点外,每个节点有且仅有一个前驱,除最后一个节点外,每个节点
有且仅有一个后继。为了同节点在二叉树中具有的前驱(即双亲)和后继(即孩子)区别开来,在容易混淆的地方,我们通常把序列中节点的前驱或后继冠以
某种遍历的名称,如把中序序列中节点的前驱称做中序前驱,节点的后继称做中序后继。

对于一颗具有n个节点的二叉树,对应的二叉链表中共有2n个指针域,其中n-1个用于指向除树根节点的其余n-1个节点,另有n+1个指针域空着。若把每个节点
中空着的左指针域和右指针域用于分别指向某种遍历次序的前驱节点和后继节点,则在遍历这种二叉树时,可由此信息直接找到在该遍历次序下的前驱节点或
后继节点,从而比递归遍历提高了遍历速度和节省了建立系统栈所使用的存储空间。这种在节点的空指针域中存放的该节点在某次遍历次序下的前驱节点或后继
节点的指针叫做线索,其中在空的左指针域中存放的指向其前驱节点的指针叫做左线索前驱线索,在空的右指针域中存放的指向其后继节点的指针叫
右线索后继线索。对一颗二叉树中的所有节点的空指针域按照某种遍历次序加线索的过程叫做线索化,被线索化了的二叉树叫做线索二叉树

二叉排序树

定义

二叉排序树(Binary Sort Tree)又称为二叉查找树(Binary Search Tree),它或者是一颗空树,或者是一颗具有如下特征的非空二叉树:

  1. 若它的左子树非空,则左子树上所有节点的关键字小于根节点的关键字;
  2. 若它的右子树非空,则右子树上所有节点的关键字均大于(若允许具有相同的关键字的节点存在,则大于等于)根节点的关键字;
  3. 左、右子树本身又是二叉排序树。

查找和插入

当二叉查找树不为空时:

  • 首先将给定值与根节点的关键字比较,若相等,则查找成功
  • 若小于根节点的关键字值,递归查左子树
  • 若大于根节点的关键字值,递归查右子树
  • 若子树为空,查找不成功

二叉排序树是一种动态树表。其特点是:树的结构通常不是一次生成的,而是在查找过程中,当树中不存在关键字等于给定值的节点时再进行插入。新插入的节点一定是一个新添加的叶子节点,并且是查找不成功时查找路径上访问的最后一个节点的左孩子或右孩子节点。如下图所示:

删除

二叉查找树的删除操作分为三种情况:

  1. 如果待删除的节点是叶子节点,那么可以立即被删除,如下图所示:

  1. 如果节点只有一个儿子,则将此节点parent的指针指向此节点的儿子,然后删除节点,如下图所示:

  1. 如果节点有两个儿子,则将其右子树的最小数据代替此节点的数据,并将其右子树的最小数据删除,如下图所示:

参考


哈夫曼树(最优二叉树)

  1. 路径和路径长度

若在一颗树中存在着一个节点序列k1,k2,….kj,使得kj是kj+1的双亲(1 ≤ i < j),
则称此节点序列是从k1~kj路径,因树中每个节点只有一个双亲节点,所以它也是这两个节点之间的唯一路径。从k1~kj
所经过的分支数称为这两点之间的路径长度,它等于路径上的节点数减1。

  1. 节点的权和带权路径长度

在许多应用中,常常将树中的节点赋上一个有着某种意义的实数,我们称此实数为该节点的权。节点的带权路径长度规定为从树根节点到该节点之间的路径长度
于该节点上权的乘积。

  1. 树的带权路径长度

树的带权路径长度定义为树中所有叶子节点的带权路径长度之和,通常记为:

$\sum_{i=1}^n {w_{i}}l_{i}$

其中n表示叶子节点的数目,wi和li分别表示叶子节点ki的权值和根到ki之间的路径长度。

  1. 哈夫曼树

哈夫曼树(Huffman)树又称最优二叉树。它是n个带权叶子节点构成的所有二叉树中,带权路径长度WPL最小的二叉树。

构造哈夫曼树

  1. 根据给定的n个权值{w1,w2,…,wn}构成二叉树集合F={T1,T2,…,Tn},其中每棵二叉树Ti中只有一个带权为wi的根节点,其左右子树为空。
  2. 在F中选取两棵根节点权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根节点的权值为左右子树根节点的权值之和。
  3. 在F中删除这两棵树,同时将新的二叉树加入F中。
  4. 重复2、3,直到F只含有一棵树为止.(得到哈夫曼树)。

平衡二叉树(AVL树)

二叉搜索树中,每一个节点的左右子树深度差的绝对值不大于1。

(a)所示为 AVL 树,而(b)所示则不是 AVL 树。

那么,如何判断一棵树是否符合 AVL 树的性质?答案就是维护每个节点的平衡因子。每个节点的平衡因子即为节点左子树的深度减去右子树的深度得到的差。在符合 AVL 性质的情况下,平衡因子只能取 -1、0、1。

正因为这样,在插入或删除一个节点之后,要从插入或删除的位置沿通向根的路径回溯,更新这些经过的节点的平衡因子。在检测到当前节点的平衡因子的绝对值大于1时,停止回溯,根据回溯路径中当前节点以及当前节点深度+1 和深度+2 两层节点的位置,选择旋转方法对二叉树的结构进行调整。

如果一棵平衡二叉树中的节点发生了变化,使二叉树不再平衡,此时需要采用平衡化旋转来调整树的结构,使得在不破坏二叉搜索树性质的情况下,让二叉树重新达到平衡。

平衡化旋转分为两种:单向旋转和双向旋转。如果回溯路径中当前节点以及下两层节点处于一条直线上,就可以采用单向旋转。如果在下两层的节点中,每一个节点都是父亲节点的右孩子,那么如图 3 所示,此时采用单向左旋。

由于此处 A<B<C,所以左旋后并不破坏二叉搜索树的性质,而刚好使得平衡因子恢复到符合 AVL 树性质的大小。

这样的过程同样可以用图来展示。举例来说,在图 4 这样一棵平衡二叉树中插入节点后,整棵树就变得不平衡了。每个节点上方的数字就是该节点的平衡因子,而长方形代表子树,长方形里面的式子等于它的深度。

要想调整二叉树的结构,这里就要用到平衡左旋了。我们取每一棵子树的根节点来代表这一整棵子树,用一共 5 个节点来演示单向左旋的过程。图 5 所示就是单向左旋的效果。

平衡树的结构最后被调整成了图 6 所示这样,而平衡因子也重新变得符合 AVL 树性质了。

同样的道理,如果需要进行平衡旋转时,当前节点的下两层节点都是父节点的左孩子,那么就需要采用单向右旋。单向右旋的道理和单向左旋非常相似,下面就主要用图来演示,不多做讲解了。单向右旋的过程如图 7~图 9 所示。

参考