二叉树

满足以下条件的树为二叉树:

  • 任一节点的左子树小于右子树

二叉树的如下:

  • 二叉树中,第 \(i\) 层最多有 \(2^{i-1}\) 个结点

  • 如果二叉树的深度为 \(K\) 。那么二叉树最多有 \(2^K-1\) 个节点

  • 设叶子节点数为 \(n_0\) ,度为二的节点数为 \(n_2\) ,那么 \(n_0=n_2+1\)

此外还有一些特殊二叉树的性质:

满二叉树:

  • 第 \(i\) 层的节点数为 \(2^{n-1}\) 个

  • 深度为 \(k\) 的满二叉树必有 \(2^k-1\) 个节点,叶子数为 \(2^{k-1}\)

  • 具有 \(n\) 个节点的满二叉树的深度为 \(\log_2(n+1)\)

完全二叉树:二叉树除了最后一层外是满二叉树,而且左子树一定比右子树满

  • \(n\) 个结点的完全二叉树的深度为 \(\\lfloor log_2n\rfloor +1\)

树之间的关系为:

graph TD

A[二叉树]-→ B[完全二叉树] B-→ C[满二叉树] A-→ D[二叉排序树] D-→ E[平衡二叉树]

二叉树的遍历

二叉树的遍历有三种:

  • 先序遍历:NLR,

  • 中序遍历:LNR

  • 后序遍历:LRN

以及最后的 层次遍历 。层次遍历可以使用队列完成。从根节点开始,将节点入队,然后出队一个节点,再将出队节点的左右孩子入队。得到的就是层次遍历

先序遍历可以简单叙述为:

void PreOrderTraverse(BTree tree){
   stacks.push(root)
   while(auto node = stacks.pop()){
      visit(node);
      stacks.push(node.right());
      stacks.push(node.left());
   }
}

使用序列还原二叉树

  • 先序
    中序:由先序遍历序列我们可以立刻确定根节点,那么在中序遍历序列中,我们定位这个找出来的根节点,该结点以左就是它的左子树,该结点以右就是它的右子树[1]

先+后不行,但是其他的都可以

平衡二叉树

当二叉树插入的序列有序时,二叉树会退化成一个链表,为了解决这个问题,就提出了平衡二叉树。平衡二叉树的左右子树的高度之差不超过一。要保证这一点,在节点插入时可能需要用到多次树的旋转。

AVL (平衡二叉树) 具有以下性质:

  • 可以是空树

  • 如果不是空树,那么左右子树都是平衡二叉树,且高度之差不超过一

AVL 中涉及到了平衡因子的概念,平衡因子就是左子树与右子树高度之差。AVL 中不存在平衡因子大于 1 的节点。平衡因子可能的取值是 -1、0、1 。

旋转

旋转分为以下几种:

  • 左旋:

    1. 使用节点的右孩子代替此节点的位置

    2. 使右孩子的左子树变为该节点的右子树

    3. 节点变为右孩子的左子树

  • 右旋:

    1. 节点的左孩子代替此节点

    2. 节点的左孩子的右子树变为节点的左子树

    3. 节点变为左孩子的右子树

插入

节点插入引起的失衡分为以下几种:

  • 左孩子的左子树导致的失衡:对节点使用一次右旋

  • 右孩子的右子树导致的失衡:对节点使用一次左旋

  • 左孩子的右子树导致的失衡:先对失衡节点的左孩子进行左旋,再对节点进行右旋

  • 右孩子的左子树导致的失衡:对失衡节点的右孩子进行右旋,再对失衡节点左左旋

最小失衡子树:在新插入的结点向上查找,以第一个平衡因子的绝对值超过 1 的结点为根的子树称为最小不平衡子树。也就是说,一棵失衡的树,是有可能有多棵子树同时失衡的。而这个时候,我们只要调整最小的不平衡子树,就能够将不平衡的树调整为平衡的树。

红黑树

红黑树是一种弱化的 AVL。主要原因是:

  • AVL 在插入时的旋转次数过多

  • 红黑树和 AVL 的时间复杂度相差并不是太多

  • 红黑树的整体性能更好

相比 AVL 而言,红黑树做出以下保证:

  • 最长路径不超过最短路径的二倍,因而近似平衡

性质如下:

  • 每个节点不是黑色就是红色

  • 根节点是黑色的

  • 红节点的两个子节点是黑色的(没有连续的红色节点)

  • 对于每个节点,从该节点到其后代叶节点的简单路径上,均包含相同数目的黑色节点

  • 插入最多两次旋转,删除最多三次旋转

红黑树在查找,插入删除的性能都是 \(O(\log n)\) ,且性能稳定,所以 STL 里面很多结构包括 map 底层实现都是使用的红黑树。

红黑树依赖于节点的颜色对节点进行调整,当节点颜色满足定义时,红黑树也就自然满足它的性质了。

节点的插入:

由于插入黑色节点的代价太大(除了根节点),因此 插入的节点的颜色都是红色 的,插入后需要根据颜色对树进行调整,主要方式为:

父节点

爷爷节点

叔叔节点

位置关系

调整方式

任意

任意

任意

无需调整

任意

父节点和叔叔节点改为黑色,爷爷节点改成红色,然后从爷爷节点向上继续调整颜色

不存在/黑

插入节点为左孩子、父节点左孩子

以父节点为轴心进行右旋

不存在/黑

插入节点为右孩子、父节点右孩子

以父节点为轴心进行右旋

不存在

插入节点为右孩子,父节点为左孩子

对父节点进行左旋

不存在

插入节点为左孩子,父节点为右孩子

对父节点进行右旋

哈夫曼树

哈夫曼树是一种带权树。节点的带权路径长度被称为代价。

在构建哈夫曼树时,需要遵循一个原则:权值越大离根越近

构建哈夫曼树[2]

对于给定的有各自权值的 \(n\) 个结点,构建哈夫曼树有一个行之有效的办法:

  • 在 \(n\) 个权值中选出两个最小的权值,对应的两个结点组成一个新的二叉树,且新二叉树的根结点的权值为左右孩子权值的和;

  • 在原有的 \(n\) 个权值中删除那两个最小的权值,同时将新的权值加入到 \(n–2\) 个权值的行列中,以此类推;

  • 重复 1 和 2 ,直到所以的结点构建成了一棵二叉树为止,这棵树就是哈夫曼树。

Last moify: 2022-12-04 15:11:33
Build time:2025-07-18 09:41:42
Powered By asphinx