码出高效读书笔记:树

1、树(tree)

树是一种常用的数据结构,它是一个由有限节点组成的一个具有层次关系的集合,数据就存在树的这些节点中。最顶层只有一个节点,称为“根节点”(root)。在分支处有一个节点,指向多个方向,如果某节点下方没有任何分叉的话,就是叶子节点。

  • 从某节点出发,到叶子节点为止,最长简单路径上边的条数,称为该节点的高度
  • 从根节点出发,到某节点边的条数,称为该节点的深度

如下图所示的树,根节点root的高度是5,深度是0;节点2的高度是4,深度是1。

树的结构特点如下:

  1. 一个节点,即只有根节点,也可以是一棵树。
  2. 其中任何一个节点与下面所有节点构成的树成为子树。
  3. 根节点没有父节点,而叶子节点没有子节点。
  4. 除根节点外,任何节点有且只有一个父节点。
  5. 任何节点可以有0-N个子节点。

每个节点至多有两个子节点的树称为二叉树,上图正好是一棵二叉树。二分法是经典的问题拆解算法,二叉树是近似于二分法的一种数据结构实现,二叉树是高效算法实现的载体,在整个数据结构领域具有举足轻重的地位。在二叉树的世界里,最为重要的概念是平衡二叉树二叉查找树红黑树


Java以递归的方式打印下图中的二叉树(前序、中序、后序):

代码如下:

package Test;
//使用递归实现的话,无非就是打印的顺序不同
public class PrintBinTree {
    //前序遍历   根节点、左节点、右节点
    public void prePrint(Node head){
        if (head == null) {
            return;
        }
        System.out.print(head.val+" ");
        prePrint(head.left);
        prePrint(head.right);
    }
    //中序遍历   左节点、根节点、右节点
    public void midPrint(Node head){
        if (head == null) {
            return;
        }
        midPrint(head.left);
        System.out.print(head.val+" ");
        midPrint(head.right);
    }
    //后序遍历   右节点、根节点、左节点
    public void backPrint(Node head){
        if (head == null) {
            return;
        }
        backPrint(head.left);
        backPrint(head.right);
        System.out.print(head.val+" ");
    }
    public static void main(String[] args) {
        Node head = new Node(1);
        head.left = new Node(2);
        head.right = new Node(3);

        head.left.left = new Node(4);
        head.left.right = new Node(5);
        head.right.left = new Node(6);
        head.right.right = new Node(7);

        PrintBinTree pbt = new PrintBinTree();
        System.out.print("前序:");
        pbt.prePrint(head);
        System.out.println();
        System.out.print("中序:");
        pbt.midPrint(head);
        System.out.println();
        System.out.print("后序:");
        pbt.backPrint(head);
    }
}

class Node{
    public int val;
    public Node left;
    public Node right;

    Node(int val){
        this.val = val;
    }
}


2、平衡二叉树

如果把上图中的所有左侧枝叶全部砍掉的话,那么剩余的部分还是树吗?很明显是的,但是它只是以“树”之名,行“链表”之实,如下面(a)图所示。如果以树的复杂结构来实现简单的链表功能,则完全埋没了树的特点。那么如此看来,对于树的使用,需要进行某种条件的约束,如下面(b)图所示,让链表一样的树变得更优层次结构,平衡二叉树就呼之欲出了!

(a)图的高度差为5,而右侧树由9与8组成的递归右子树的高度差为1,高度差是一棵树是否为平衡二叉树的决定条件。

平衡二叉树的性质如下:

  1. 树的左右高度差不能超过1。
  2. 任何往下递归的左子树与右子树,必须符合第一条性质。
  3. 没有任何节点的空树或只有根节点的树也是平衡二叉树。

3、二叉查找树

二叉查找树又称二叉搜索树(二叉排序树)。Java集合的最终目的就是加工数据,二叉查找树也是如此。树如其名,二叉查找树非常擅长数据查找。

二叉查找树额外增加了如下要求:

  • 对于任意节点来说,它的左子树上所有节点的值都小于它,而它右子树上所有节点的值都大于它。

查找过程从树的根节点开始,沿着简单的判断向下走,小于节点值的往左走,大于节点值的往右走,知道找到目标数据或者到达叶子节点还未找到。

遍历所有节点的常用方式有三种:

  1. 前序遍历:顺序是根节点、左节点、右节点。
  2. 中序遍历:顺序是左节点、根节点、右节点。
  3. 后序遍历:顺序是做节点、右节点、根节点。

根据二叉查找树的性质要求,来“美化”一下最开始的(a)“链表树”和(b)“平衡二叉树”。要牢牢遵循“对于任意节点来说,它的左子树上所有节点的值都小于它,而它右子树上所有节点的值都大于它”的要求。

(a)图的红色节点8和节点9进行互换,节点9成为节点8的右子树,形成图(b);图(c)的红色节点8移动到左子树上,把紫色的节点2与节点4互换一下,形成图(d)。经过调整后,再看任何递归字数,都是符合二叉查找树的要求,所以两棵新树都是二叉查找树。但明显右下方这棵树要优于右上方那棵,因为更加平衡,查找效率更高。同时可以看出,二叉查找树容易构造,但是如果缺少约束条件,很可能往一个方向野蛮生长,成为查找复杂度为O(n)的树。所以二叉查找树需要引入一种监测机制,随着新值的插入动态地调整树结构,那么如何来调整呢?红黑树就是来解决这个问题的。

二叉查找树由于随着数据不断地增加或删除容易失衡,为了保持二叉树重要的平衡性,有很多算法的实现,如AVL树、红黑树、SBT(Size Balanced Tree)、Treap(树堆)等。考虑到在Java底层框架很多算法实现以红黑树为基础,所以简单介绍一下AVL树,然后重点介绍红黑树。

4、AVL树(平衡二叉查找树)

AVL树是以苏联数学家Adelson-Velsky和Landis名字命名的平衡二叉树算法,可以使二叉树的使用效率达到最大化。

AVL树是一种平衡二叉查找树,增加和删除节点后通过属性旋转重新达到平衡。

  • 右旋是以某个节点为中心,将它沉入当前右子节点的位置,而让当前的左子节点作为新树的根节点,也称为顺时针旋转。
  • 左旋是以某个节点为中心,将它沉入当前左子节点的位置,而让当前的右子节点作为新树的根节点,也称为逆时针旋转。

AVL树就是通过不断旋转来达到树平衡的,下方的左旋和右旋只是旋转操作层面的简单示意图,我们应体会如何通过旋转达到一种新的平衡状态,不再基于插入和删除进行展开,下图是右旋示意图:

上图左侧是非平衡状态,需要进行平衡化处理,根节点的左子树与右子树高度差超过1,向右旋转。在旋转过程中,节点15成为新的根节点,而节点16移动到节点17的左节点上。而左旋转则反之,如下图所示:

AVL树一定要保证以下两点,才能被称为是平衡查找二叉树:

  • 左右子树的高度差不能超过1.
  • 任一节点的左子树上的节点值要小于它,右子树上的节点值要大于它。

5、红黑树

红黑树是1972年发明的,当时称为对称二叉B树,1978年得到优化,正式命名为红黑树。它的主要特征是在每一个节点上增加一个属性来表示节点的颜色,可以是红色,也可以是黑色

红黑树和AVL树类似,都是在进行插入和删除元素时,通过特定的旋转来保持自身平衡,从而获得较高的查找性能。与AVL树相比,红黑树并不追求所有递归字数的高度差不超过1,而是保证从根节点到叶尾的最长路径不超过最短路径的2倍,所以它的最坏运行时间也是O(logn)。红黑树通过重新着色和左右旋转,更加高效地完成了插入和删除操作后的自平衡调整。当然,红黑树在本质上还是二叉查找树,它额外引入了5个约束条件:

  1. 节点只能是红色或者黑色。
  2. 根节点必须是黑色。
  3. 所有NIL(Nothing In Leaf,是红黑树中特殊的存在,即在叶子节点上不存在的两个虚拟节点,它是红黑树旋转的假设性理论基础,默认为黑色)节点都是黑色的。
  4. 一条路径上不能出现相邻的两个红色节点。
  5. 在任何地递归子树内,根节点到叶子节点的所有路径上包含相同数目的黑色节点。

以上总结起来就是“有红必有黑,红红不相连”,上述5个约束条件保证了红黑树的新增、删除、查找的最坏时间复杂度均为O(logn)。如果一个树的左子节点或右子节点不存在,则均认定是黑色。红黑树的任何旋转在3次内均可完成。

红黑树的平衡性并不如AVL树,它维持的只是一种大致上的平衡(相对的平衡),并不严格保证左右子树的高度差不超过1.这导致在相同节点数的情况下,红黑树的高度可能更高,也就是说,平均查找次数会高于相同情况下的AVL树。

  • 在插入时,红黑树和AVL树都能在至多两次旋转内恢复平衡。
  • 在删除时,由于红黑树只追求大致上的平衡,因此红黑树能在至多三次旋转内恢复平衡,而追求绝对平衡的AVL树,则至多需要O(logn)次旋转。

可以得到以下结论:

  • 面对频繁的插入和删除,红黑树更合适。
  • 面对低频修改、大量查询时,AVL树将更合适。

示例:我们对红黑树与AVL树进行以下操作,按照顺序依次插入36、34、37、33、35、32,红黑树与AVL树结构的区别如下图所示:

猜你喜欢

转载自blog.csdn.net/weixin_41047704/article/details/86502693
今日推荐