红黑树属于二叉树。
首先说说二叉树的优点,二叉树的左孩子比自己小,右孩子比自己大,这样在进行查找的时候,就可以像二分查找一样每一次都可以缩小要查找的值的范围。
如果这个二叉树是平衡二叉树的话,查找的速度可以想象(因为不需要遍历所有的节点)。
但是,相反考虑,如果这个二叉树退化成了链表,这样就会出现最糟糕的行为(因为对于链表的查找就是需要一个个进行遍历,而且链表本身还占用了更多内存)。
红黑树就属于变种的二叉树,他的目的是为了让二叉树尽可能平衡,并且新增和删除时尽可能不做更多的操作,在平均使用的情况下显现出良好的性能。
那红黑树是怎么做到让二叉树尽可能平衡的呢?
红黑树规定了:
1.首先红黑树属于二叉树(这不是废话蛮)。
2.红黑树的节点非红即黑。每加入一个节点,需要给节点涂上颜色。
3.根节点必须是黑色。
4.不能有连续的两个红节点(即红节点的儿子和父亲都必须为黑)。
5.从根节点到叶节点的每一条路径上的黑节点数量相同。
第四条规则和第五条规则是保证红黑树平衡最重要的规定。第四条规则很好理解,第五条规则是什么意思是呢?
ABD,ABE,AC这三条路径中,黑色节点的和都是2,即满足第五条规定。
那么,现在让我们来谈谈红黑树的增加和删除(查找和二叉树的查找一样,就不详细描述了)。
红黑树的增加:
其实红黑树的增加所做的一些操作,都是为了使得新增的节点都是红色。就上图的情况来说,如果新增的节点比D的值要小,那就会成为D的左孩子,要满足红黑树的规定,新增加的孩子颜色就需要变成红(给黑色节点添加儿子节点的操作是最简便的)。
但是实际上,有些情况可能是,新增加的节点的父亲为红色,而红黑树规定不能有连续的两个红色节点,那这时候,就需要用到AVL的旋转,再配以适当的更改节点的颜色了。
这里要分两种情况。
第一,当父亲节点的兄弟节点为黑色或者父亲节点没有兄弟节点时。(这里是上图的一部分)
如图,新增加的节点为G,父亲为红色的F,父亲的兄弟节点没有,这时就满足第一个情况。
这时候,因为G是F的左孩子,所以对F做左单旋转(旋转的知识可查看AVL树)。
然后更改节点的颜色:
为什么旋转之后就能满足红黑树的规定了呢?
首先,规定4肯定满足了,最麻烦的是规定5,从根节点到叶节点的每一条路径上的黑节点数量相同。
旋转之前,DF路径上只有一个黑色,加上G,G肯定要是黑色,这是DFG路径就有两个黑色,这样的话ABDFG就是3个黑色节点,而AC,ABE路径只有两个黑色节点,违反规定5。
而旋转之后,F成为了原来的D,G成为了原来的F,原来DF路径现在成为了FG路径。即:
旋转就是为了让原来的ABDF路径上少一个黑色,让这个黑色成为ABFG,ABFD公共的黑色,这样每条路径上的黑色节点数量就想等了。
第二种情况多少要复杂一点:
当父亲节点的兄弟节点为红色时(接着上图):
如果新增了一个节点H,H比G小,成为了G的左孩子,而G的兄弟节点D也是红色
这时候就不能想第一种情况来处理(详细可以自己试试,发现肯定会失败的)。
记住,增加节点的时候,都是想方设法的让新增加的节点都是为红色,父亲节点为黑色。
那么,这里就要对FDG进行颜色更换了。
即,F变为红色,GD变为黑色,那么,H就可以顺理成章的成为黑色节点G的左孩子了。
但是,这里又出现问题了,连续的两个BF节点,违反了规定4,那么,不要着急,我们之前做的都没错,这时候,就吧F想象成新插入的节点,B当做父亲节点来处理,这时候发现父亲节点B的兄弟节点为C,是黑色的,就回到了第一种情况了。
这样,节点就插入成功了。
是不是发现,节点的插入会导致红黑树是相对比较平衡的呢?在实际生产环境中,红黑树的平衡性能确实要比一般的二叉树要好,而相对于AVL为平衡做巨多的操作,红黑树的操作就相对少了很多。