二分搜索树是为了快速查找而生,它是一颗二叉树,每个节点只有一个元素(值或键值对),左子树全部节点的值均小于父节点的值,右子树全部的值均大于父节点的值,左右子树也是一颗二分搜索树,并且没有键值相等的节点。它的查找、插入和删除的时间复杂度都与树高成比例,指望值是O(log n)。html
可是插入数组如[],二分搜索树的缺点就暴露出来了,二分搜索树退化成线性表,查找的时间复杂度达到最坏时间复杂度O(n)。算法
那有没有插入和删除操做都能保持树的完美平衡性(任何一个节点到其叶子节点的路径长度都是相等的)?数组
有,B树。B树是一种自平衡的树,根节点到其叶子节点的路径高度都是同样的,可以保持数据有序(经过中序遍历能获得有序数据)。B树一个节点能够拥有2个以上的子树,如2-3树、2-3-4树甚至2-3-4-5-6-7-8树,它们知足二分搜索树的性质,但它们不属于二叉树,也不属于二分搜索树。ide
2-3-4树的完美平衡,每条从根节点到叶子节点的路径的高度都是同样的函数
2-3-4树有如下节点组成:学习
2-节点,含有一个元素(值或键值对)和两个子树(左右子树),左子树全部的值均小于父节点的值,右子树全部的值均大于父节点的值;动画
3-节点,含有两个元素和三个子树,左子树全部的值均小于父节点最小元素的值,中间子树全部的值均位于父节点两个元素之间,右子树全部的值均大于父节点最大元素的值;spa
4-节点,含有三个元素和四个子树,节点之间的比较也知足二分搜索树的性质。设计
2-3-4树的查找相似二分搜索树的查找。3d
2-3-4树的插入算法是消除当前节点是4-节点,将4-节点分解成多个2-节点,中间的2-节点与父节点合并成3-节点或4-节点。
沿着连接向下进行变换分解4-节点分为两种状况:
1)4-节点做为根节点,分解成3个2-节点,中间的2-节点做为根节点;
2)当前节点为4-节点,分解成3个2-节点,中间的2-节点与父节点合并成3-节点或4-节点;
图:沿着连接向下进行变换,分解4-节点
在沿着左右连接向下进行变换的同时,也会进行命中查找。若是元素是键值对的话,查找命中将旧的值赋值为新的值;若是元素是一个值的话,查找命中将忽略之,由于二分搜索树须要知足没有相等的元素;若是须要支持重复的元素,则在元素对象添加count属性,默认为1。
若是查找未命中,则将待插入元素插入在叶子节点上。树底下插入一个元素只有两种状况:向2-节点中插入和向3-节点中插入。
图:树底下插入一个元素
2-3-4树的删除算法是消除当前节点是2-节点,向兄弟节点或父节点借一个元素过来。
从根节点的左孩子开始,沿着左连接向下进行变换能够分为三种状况:
1)当前节点不是2-节点,跳过;
2)当前节点是2-节点,兄弟节点是2-节点,将当前节点、父节点最小元素和兄弟节点合并为4-节点,当前节点变换成4-节点;
3)当前节点是2-节点,兄弟节点不是2-节点,将兄弟节点的最小元素移到父节点,父节点的最小元素移到当前节点,当前节点变换成3-节点。
图:沿着左连接向下进行变换
从根节点的右孩子开始,沿着右连接向下进行变换也一样分为三种状况:
1)当前节点不是2-节点,跳过;
2)当前节点是2-节点,兄弟节点是2-节点,将当前节点、父节点的最大元素和兄弟节点合并为4-节点,当前节点变换成4-节点;
3)当前节点是2-节点,兄弟节点不是2-节点,将兄弟节点的最大元素移到父节点,父节点的最大元素移到当前节点,当前节点变换成3-节点。
图:沿着右连接向下进行变换
学习过删除最小元素和删除最大元素算法以后,删除任意元素的算法天然就简单了。删除任意元素算法须要先进行命中查找,若查找命中,则将右子树的最小值替换掉待删除元素,而后将右子树进行删除最小元素的算法。
2-3-4树虽知足二分搜索树的性质,但不是一颗二分搜索树。若是指望它是一颗二分搜索树,就须要将3-节点和4-节点替换为多个2-节点,还须要注明元素之间的关系(用红连接表示)。
图:替换3-节点
图:替换4-节点
可是存在一个问题,2-3-4树由于3-节点的不一样表示会有不少种不一样的红黑树,3-节点既能够左倾,也能够右倾。因此为了保证树的惟一性,3-节点只考虑左倾,固然你也能够只考虑右倾。
这样对于任何一颗2-3-4树,只考虑左倾的状况下,都能获得惟一的一颗对应的红黑树,这种树也叫左倾红黑树,相对比较减小了复杂性,设计更容易被实现。
红黑树的查找算法和二分搜索树同样。
关于连接的颜色变换只跟颜色转换有关,而旋转不会改变连接的颜色变换,只在被红连接指向的节点变成红色,被黑连接指向的节点变成黑色。
旋转是将不知足红黑树性质的3-节点和4-节点进行旋转,若是3-节点出现右连接,则将右连接经过左旋转变成左连接;若是4-节点出现一个红节点连着两条红连接,则将4-节点配平。
图:左旋转
图:右旋转
图:3-节点和4-节点的旋转
颜色转换只应用于4-节点。
图:颜色转换
回顾以前的2-3-4树的插入算法,它有两个过程:沿着连接向下进行分解4-节点和树底下插入一个元素。
红黑树的插入算法和2-3-4树的插入算法相似,它不只包含前面两个过程,还增长了向上进行变换的过程,此过程是将3-节点左倾和4-节点配平。
红黑树插入算法会先从根节点开始,沿着左右连接向下进行变换,目的是为了分解4-节点。若是该节点的左右孩子都是红节点,则经过flipColors方法进行颜色转换,接着进行下一个子节点;若是不是,则直接进行下一个子节点。
到达树底的时候,则意味着能够开始插入新的元素。
若是红黑树目前是一颗空树,插入红色的元素做为第一个节点,而后该节点变成黑色。若是不是一颗空树,插入元素分为三种状况:向2-节点插入新元素、向3-节点插入新元素和向4-节点插入新元素。
向2-节点插入新元素很简单,若是新元素的值小于父节点,直接插入红色的节点便可;若是新元素的值大于父节点,则产生一个红色右连接,插完以后则将3-节点进行左旋转,将右连接变成左连接,被红连接指向的节点变成红色,被黑连接指向的节点变成黑色。
图:向2-节点插入新元素
由于前面的3-节点进行过旋转,此时的3-节点确定知足左倾红黑树的性质。向3-节点插入新元素分为三种状况:
1)新元素的值位于3-节点中的两元素之间;
2)新元素的值小于3-节点中的最小元素;
3)新元素的值大于3-节点中的最大元素。
图:向3-节点插入新元素
向4-节点插入新元素以前须要先进行颜色转换,才能够进行插入新的元素。
图:向4-节点插入新元素
插完新元素以后须要知足红黑树的性质,则在沿着父节点的连接向上进行变换,具体作法和向3-节点插入新元素的作法相似,经过左旋转将3-节点左倾和左右旋转将4-节点配平,没有颜色转换。
Code:红黑树插入算法
红黑树删除算法也须要进行旋转和颜色转换操做,在插入算法中为了待插入元素所在的节点不是4-节点,因此在沿着左右连接向下进行变换时将4-节点分解成3个2-节点,中间的2-节点与父节点合并;而在删除算法中为了待删除元素所在的节点不是2-节点,因此在沿着左右连接向下进行变换时将2-节点向其它不是2-节点的节点(兄弟节点或父节点)借一个元素过来,合并成3-节点。
因此,只要是2-节点的节点,若是兄弟节点不是2-节点,就将兄弟节点的与父节点邻近的元素移到父节点,而父节点将与当前节点邻近的元素移到当前节点;若是兄弟节点是2-节点,则将父节点的与当前节点邻近的元素移到当前节点。(是否是很绕?待会在后面删除最值算法中详细给出)
而后删除完一个元素以后须要进行修复调整,将这个树知足红黑树的性质。若是右连接是红色,将右连接经过左旋转变成左连接;若是有连续的左连接,经过右旋转配平,而后进行颜色转换。
删除最小元素算法和二分搜索树同样,一直递归它的左孩子,直到它的左孩子为空才进行删除这个最小元素。可是红黑树在递归的同时如何旋转和颜色转换是个问题。
删除最小元素算法一直沿着左连接向下进行转换,对照2-3-4树,咱们能够给出三种状况,从根节点开始:
1)当前节点(父节点位置)的左子节点不是2-节点,直接进行下一个节点(左子节点);
2)当前节点的左子节点和右子节点都是2-节点,则将左子节点、当前节点的最小元素和右子节点合并成4-节点,而后进行下一个节点;
3)当前节点的左子节点是2-节点,右子节点不是2-节点,则将右子节点的最小元素移到当前节点的位置,当前节点的最小元素移到左子节点,而后进行下一个节点。
图:沿着左连接向下进行转换
直到某元素左孩子为空的时候,此时的元素是这个树的最小元素。由于经过前面的转换,最小元素确定被一个红连接指向,删除这个元素以后经过balance方法修复调整为红黑树。
Code:删除最小元素算法
删除最大元素算法和删除最小元素算法相似的,也分为三种状况:
1)当前节点(父节点位置)的右子节点不是2-节点,直接进行下一个节点(右子节点);
2)当前节点的右子节点和左子节点都是2-节点,则将右子节点、当前节点的最大元素和左子节点合并成4-节点,而后进行下一个节点;
3)当前节点的右子节点是2-节点,左子节点不是2-节点,则将左子节点的最大元素移到当前节点的位置,当前节点的最大元素移到左子节点,而后进行下一个节点。
图:沿着右连接向下进行变换
学习过前面的删除最小元素算法和删除最大元素算法,删除任意元素会变得很简单。删除最小元素算法会一直沿着左连接向下进行变换,删除最大元素算法会一直沿着右连接向下进行变换,而删除任意元素算法须要同时存在着左右连接向下进行变换。
删除任意元素算法须要先进行命中查找,在命中查找的过程当中会进行沿着左右连接向下变换,若是查找命中则将右子树的最小元素替换掉待删除元素,而后进行右子树的删除最小元素算法;若是查找未命中,则直接返回balance函数,向上将3-节点左倾或将4-节点配平。
学习完上面的算法以后,能够总结下红黑树的性质:
1)每一个节点或是红色的,或是黑色的;
2)根节点是黑色的;
3)每一个叶子节点(NIL)是黑色的;
4)若是一个节点是红色的,则它的两个子节点都是黑色的(NIL节点也是黑色的);
5)对每一个结点,从该节点到其全部后代叶子节点的简单路径上,均包含相同数目的黑色节点(黑连接平衡)。
喜欢本文的朋友,欢迎关注公众号「算法无遗策」,收看更多精彩内容