完全二叉树的理解和相关公式推导

925 阅读6分钟

完全二叉树简介

  • 完全二叉树是指,所有节点最多有两个叉
  • 并且每层都得从左向右排列
  • 必须上一层排满了,才能排下一层
  • 是指,当前节点有几个子节点。在二叉树中,只可能有三种不同的度节点
    • 2度节点,即有左右两个子元素的节点
    • 1度节点,只有一个子元素的节点,由于完全二叉树的特性,1 度节点最多只有一个
    • 0度节点,即叶子节点。叶子节点最多出现在后两层。

如下是14个节点的完全二叉树。

           0
         /   \
        /     \
       /       \
      /         \
     1           2
    / \         / \
   /   \       /   \
  3     4     5     6
 / \   / \   / \   /
7   8 9  10 11 12 13

其实上面的完全二叉树,是可以从上往下,从左往右,放入到一个数组中,为了方便表示,上图直接把节点的值写成了数组下标。

几个公式和其证明

本文中的 log 如果忽略底数,就是以 2 为底,即 log2

nodeNum = edgeNum + 1

在任何不循环的树中,其节点数量nodeNum 和边数量edgeNum的关系是 nodeNum = edgeNum + 1

  • 例如当前这棵树只有一个节点,那么 nodeNum = 1edgeNum = 0
  • 之后,在这棵树上,每添加一个点,必须要添加一个边。例如再添加n个点,那么nodeNum = n + 1edgeNum = n
  • 通过上述两个和n相关的关系式,可以得出 nodeNum = edgeNum + 1

叶子节点推算公式 N0 = (n-1)*Nn + (n-1)*Nn-1 + ... + 2*N3 + N2 + 1

在一棵度最大为 4(这里用 n 表示,比较好,但是那个数学公式用代码表述比较麻烦,所以先用实数 4 举例)的树中,假设

  • 0 度节点有n0
  • 1 度节点有n1
  • 2 度节点有n2
  • 3 度节点有n3
  • 4 度节点有n4

那么总节点数nodeNum的值为nodeNum = n0 + n1 + n2 + n3 + n4 从边的角度讲:

  • 0 度节点有 0 个边
  • 1 度节点有 1 个边
  • 2 度节点有 2 个边
  • 3 度节点有 3 个边
  • 4 度节点有 4 个边

所以 edgeNum = 0*n0 + 1*n1 + 2*n2 + 3*n3 + 4*n4
又有nodeNum = edgeNum + 1nodeNum = n0 + n1 + n2 + n3 + n4
可以得出 1*n1 + 2*n2 + 3*n3 + 4*n4 + 1 = n0 + n1 + n2 + n3 + n4
即叶子节点数量n0n0 = 3*n4 + 2*n3 + n2 + 1
这个公式适用于任何多叉树叶子节点的计算,

  • 3 叉树为:n0 = 2*n3 + n2 + 1
  • 5 叉树为:n0 = 4*n5 + 3*n4 + 2*n3 + n2 + 1
  • n 叉树为:N0 = (n-1)*Nn + (n-1)*Nn-1 + ... + 2*N3 + N2 + 1(这里因为 md 不能用下标 n 这种表示,所以把 n 大写了。)

二叉树叶子节点公式 n0=n2+1

其中,二叉树的情况为:
节点的情况

  • 0 度节点有n0
  • 1 度节点有n1
  • 2 度节点有n2

边的情况:

  • 0 度节点有 0 个边
  • 1 度节点有 1 个边
  • 2 度节点有 2 个边

按照上述推导以后得出 n0 = n2 + 1,即叶子节点2度节点多一个

二叉树左子树位置计算公式 nl = 2n + 1

假设现在有个一个节点,它在数组中下标为n,它和其左子树下标nl的关系是nl = 2n + 1,那右子树nr就是nl + 1nr = 2n + 2。证明如下。
设想这棵二叉树只绘制填充到当前元素左子树被填充的情况(可以参考成下图,5 就是此时的 n)。

           0
         /   \
        /     \
       /       \
      /         \
     1           2
    / \         / \
   /   \       /   \
  3     4     5     6
 / \   / \   /
7   8 9  10 11
  • 所有后续兄弟节点都没有子元素,即都是叶子节点

  • 所有前序兄弟都被填满,即所有前续兄弟都是2度节点

  • 叶子节点数量 n0: n0 = 后续兄弟数量 + 前序兄弟 * 2 + 1,+1 是因为本节点的左子节点也是叶子节点。

  • 此时nl的位置就是当前节点位置加上所有叶子节点的数量即:nl = n + n0(等式 1)

  • 2度节点的数量为n2: 又由于当前节点之前所有节点都被填充满了,所以当前节点之前的节点,都是2度节点,那就是从 0 到 n-1,总共有 n 个,即n2 = n(等式 2)

  • 我们从上节推算出了二叉树中叶子节点2度节点的数量关系为n0 = n2 + 1(等式 3)

带入上述等式

nl = n  + n0
n2 = n;
n0 = n2 + 1;
// 带入等式:
nl = n  + n2 + 1
   = n + n + 1
   = 2n + 1

那么右子树的位置就是 2n+2,n 的父元素的位置就是(n - 1) / 2,对结果向下取整。

完全二叉树的层高:(log(n+1) + 1) 向下取整

  • 在完全二叉树中,第 l 层(层数从 1 开始计算),最多存放 2^(l-1) 个节点。例如第 1 层有 1 个节点(根节点),第 2 层有 2 个节点,第 3 层有 4 个节点。
  • 那么本层最后一个元素的就是第 1 + 2 + 4 + ... + 2^(l-1)个元素,即2^l - 1
  • 上一层最后一个元素就是第2^(l-1) - 1个。
  • 那么第 l 层元素存放的就是 第2^(l-1)2^l - 1个。

例如当前有个元素,它的下标为 n,那么,它就是第n+1个元素,假设它所处的层级是第 l 层,就有表达式:2^(l-1) <= n+1 < 2^l(注意,2^l 即下层第一个元素),取对数得l-1 <= log(n+1) < l
即 l 层高为:log(n+1) + 1向下取整。eg:

  • 索引为 6 的元素,层高为 log(6+1) + 1,3.88,向下取整为 3。
  • 索引为 7 的元素,层高为 log(7+1) + 1,4,向下取整为 4。

优先级队列(二叉堆)

数据结构中有一种结构叫,二叉堆,它其实就是完全二叉树,只是存储在数组中时,根节点从索引 1 开始存入。如下是一个二叉堆放在数组中的示意图。

           1
         /   \
        /     \
       /       \
      /         \
     2           3
    / \         / \
   /   \       /   \
  4     5     6     7
 / \   / \   / \   /
8   9 10 11 12 13 14

二叉堆左子树位置计算公式 nl = 2n

  • 等式 1n0 = n2 + 1和索引或者说二叉树存储方式无关,仍然成立。
  • 等式 2n2 = n不再成立,因为此时 n 代表的不再是索引,而是第几个。第 n 个节点,其前面的的节点都是2度节点这个理论仍然成立,即2度节点的数量为n2 = n - 1
  • 等式 3nl = n + n0仍然成立,因为这个也和索引起始点无关,n 和 nl 的差值,永远是 n0

带入三个等式得:nl = n+n0 = n+n2+1 = n+n-1+1 = 2n

二叉堆的层高 l = (log(n)+1) 向下取整

  • 第 l 层元素存放的就是 第2^(l-1)2^l - 1个。这个结论仍然成立(l从1开始)。

假设要求取第n个元素在第几层,那么就有2^(l-1) <= n < 2^l,对数计算后l就是log(n) + 1向下取整。

  • 第 6 个元素(即索引为6),层高为 log(6) + 1,3.58,向下取整为 3。
  • 索引为 7 的元素,层高为 log(7) + 1,3.88,向下取整为 3。
  • 索引为 8 的元素,层高为 log(8) + 1,4,向下取整为 4。