要说的红黑树就是就是一颗非严格均衡的二叉树,均衡二叉树又是在二叉搜索树的基础上增加了自动维持平衡的性质,插入、搜索、删除的效率都比较高。红黑树也是实现TreeMap存储结构的基石。典型的用途是实现关联数组.
它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。
1、红黑树规则特点
- 节点分为红色或者黑色;
- 根节点必为黑色;
- 叶子节点都为黑色,且为null;
- 连接红色节点的两个子节点都为黑色(红黑树不会出现相邻的红色节点);
- 从任意节点出发,到其每个叶子节点的路径中包含相同数量的黑色节点;
- 新加入到红黑树的节点为红色节点;
因为红黑树也是均衡二叉树,需要具备自动维持平衡的性质,上面的6条就是红黑树给出的自动维持平衡所需要具备的规则
为什么说新加入到红黑树中的节点为红色节点
从规则4中知道,当前红黑树中从根节点到每个叶子节点的黑色节点数量是一样的,此时假如新的黑色节点的话,必然破坏规则,但加入红色节点却不一定,除非其父节点就是红色节点,因此加入红色节点,破坏规则的可能性小一些,下面我们也会举例来说明。
什么情况下,红黑树的结构会被破坏呢?破坏后又怎么维持平衡,维持平衡主要通过两种方式【变色】和【旋转】,【旋转】又分【左旋】和【右旋】,两种方式可相互结合。
2、红黑树节点插入
一般插入红色结点进行变色不会有问题,结合红黑树的规则进行向上满足,可以完成,当出现通过变色无法维持平衡规则的问题,这是就要用到了【旋转】
旋转包括【左旋】和【右旋】,左旋:逆时针旋转两个节点,让一个节点被其右子节点取代,而该节点成为右子节点的左子节点。右旋:
顺时针旋转两个节点,让一个节点被其左子节点取代,而该节点成为左子节点的右子节点。你会发现红黑树的平衡和平衡二叉树的平衡规则一样的。
插入旋转总结
无需调整 | 【变色】即可实现平衡 | 【旋转+变色】才可实现平衡 | |
---|---|---|---|
情况1: | 当父节点为黑色时插入子节点 | 空树插入根节点,将根节点红色变为黑色 | 父节点为红色左节点,叔父节点为黑色,插入左子节点,那么通过【左左节点旋转】 |
情况2: | - | 父节点和叔父节点都为红色 | 父节点为红色左节点,叔父节点为黑色,插入右子节点,那么通过【左右节点旋转】 |
情况3: | - | - | 父节点为红色右节点,叔父节点为黑色,插入左子节点,那么通过【右左节点旋转】 |
情况4: | - | - | 父节点为红色右节点,叔父节点为黑色,插入右子节点,那么通过【右右节点旋转】 |
3、红黑树的平衡和AVL树的平衡区别
1. AVL树(平衡二叉树)
AVL树是带有平衡条件的二叉查找树,一般是用平衡因子差值判断是否平衡并通过旋转来实现平衡,左右子树树高不超过1,和红黑树相比,AVL树是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1)。不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而的英文旋转非常耗时的,由此我们可以知道AVL树适合用于插入与删除次数比较少,但查找多的情况。
由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树。当然,如果应用场景中对插入删除不频繁,只是对查找要求较高,那么AVL还是较优于红黑树。
2. 红黑树
一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,可以是红或黑(非红即黑)。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍,因此,红黑树是一种弱平衡二叉树(由于是弱平衡,可以看到,在相同的节点情况下,AVL树的高度低于红黑树),相对于要求严格的AVL树来说,它的旋转次数少,所以对于搜索,插入,删除操作较多的情况下,我们就用红黑树。
区别:
红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。
4、红黑树节点删除
相比较于红黑树的节点插入,删除节点更为复杂,我们从子节点是否为null和红色为思考维度来讨论。
4.1 子节点至少有一个为null
当待删除的节点的子节点至少有一个为null节点时,删除了该节点后,将其有值的节点取代当前节点即可,若都为null,则将当前节点设置为null,当然如果违反规则了,则按需调整,如【变色】以及【旋转】。
4.2 子节点都是非null节点
第一步:找到该节点的前驱或者后继
前驱:左子树中值最大的节点(可得出其最多只有一个非null子节点,可能都为null);
后继:右子树中值最小的节点(可得出其最多只有一个非null子节点,可能都为null);
前驱和后继都是值最接近该节点值的节点,类似于该节点.prev = 前驱,该节点.next = 后继。
第二步:将前驱或者后继的值复制到该节点中,然后删掉前驱或者后继
如果删除的是左节点,则将前驱的值复制到该节点中,然后删除前驱;如果删除的是右节点,则将后继的值复制到该节点中,然后删除后继;
这相当于是一种“取巧”的方法,我们删除节点的目的是使该节点的值在红黑树上不存在,因此专注于该目的,我们并不关注删除节点时是否真是我们想删除的那个节点,同时我们也不需考虑树结构的变化,因为树的结构本身就会因为自动平衡机制而经常进行调整。