多路查找树
2-3查找树(最简单B树)
多路查找树参考链接
对二叉树的一种改进:原有的二叉树是2-结点(一个键、两条链),而现在引入3-结点(两个键、三条链)。
定义: 最简单的B树,要求一棵2-3查找树要么为空,要么由以下结点组成:
(1)、2-结点的左链指向小于该结点的值;右链指向大于该结点的值。
(2)、3-结点左链指向小于该结点的值;中链指向位于该结点的两个键值之间的值;右链指向大于该结点的值。
查找操作(图片来于第四版算法)
易知,查找操作类似于二叉查找树的查找
插入操作(图片来于第四版算法)
(1)、最后插入位置是2-结点:直接将该结点转换为3-结点即可!
(2)、最后插入位置是3-结点:临时创建4-结点再分裂
(3)、向一个父结点是2-结点的3-结点中插入:
(4)、向一个父结点是3-结点的3-结点中插入:
(5)、分解根结点:
删除操作【复习二叉查找树的三种删除操作,图片来于大话数据结构】
(1)、删除非叶子结点:中序遍历下,用待删除节点的后继节点覆盖它即可。如下图的,要删除节点4,直接使用后继6覆盖掉4.
(2)、删除叶子节点:
a、叶子节点是3-结点:直接删除该值。如下图,删除9,因为它所在3-结点,因此直接删除即可。
b、叶子结点是2-结点:
b-1、叶子结点的父节点是2-结点,且兄弟结点是3-结点:将父节点移动到当前位置,再将兄弟节点中最接近当前位置的key移动到父节点中即可。
b-2、叶子结点的父节点是2-结点,兄弟结点也是2-结点:
b-3、叶子结点的父节点是3-结点:父节点分裂为2-结点
b-4、叶子结点位于满二叉树中:
2-3查找树的性质
1、高为h的2-3查找树所包含的节点数大于等于高度为h的满二叉树的节点数,即至少有2^h-1个节点。
2、任意空链接到根节点的路径长度是相等的。即2-3树的局部变换不会影响全局的有序性和平衡性。
3、二叉查找树是自顶向下生长;而2-3查找树是自底向上生长(B树都是自底向上生长的)。
4、2-3查找树在插入后可以保证树的平衡状态,即使在最坏情况下是满二叉树,其高度也为logN。相较于普通的二叉查找树在最坏情况下退化为线性结构,确保了时间复杂度。
红黑树
红黑树(对应2-3-4树)和左倾红黑树(对应2-3树)
左倾红黑树既是二叉查找树,也是2-3树。2-3树的深度很小,平衡性好,效率高,但是其有两种不同的结点,实际代码实现比较复杂。而红黑树用红链接表示2-3树中另类的3-结点,统一了树中的结点类型,使代码实现简单化,又不破坏其高效性。
左倾红黑树定义
2-3树和红黑树
对2-3树进行编码,基本思想是利用标准的二叉查找树(完全由2-结点构成)和一些额外信息(替换3-结点)来表示2-3树。我们将树中链接分为两种:
红链接:将两个2-结点连接构成一个3-结点
黑链接:2-3树中普通链接
因此定义为:(左倾)红黑树是含有红黑链接并满足下列条件的二叉查找树:
1)、红链接均为左链接【左倾红黑树】;
2)、没有任何一个结点能同时和两条红链接相连;
3)、该树是完美黑色平衡的,即任意空链接到根结点路径上的黑链数量相同。
左倾红黑树平衡化
左旋(当某个结点的左子结点为黑色,右子结点为红色)
右旋(当某个结点的左子结点为红色,且左子结点的左子结点也为红色。注意右旋后反转颜色)
左倾红黑树插入操作
向2-结点插入(又分为插入值小于该2-结点、大于2-结点两种情况)
向3-结点插入(又分为插入值大于、小于、介于二者之间三种情况)
总结:操作时用2-3树思想实现,最后用左倾红黑树来表示即可。
/*
* 左倾红黑树实现
*/
public class RedBlackTree {
public Node root = null;// 根结点
private int nodeCount;// 结点个数
private static final boolean RED = true;
private static final boolean BLACK = false;
// 当前结点和父结点之间链接是否为红链接
public boolean isRed(Node x) {
if (x == null) {
return false;
}
return x.color == RED;
}
// 树中元素个数
public int size() {
return nodeCount;
}
/* 左旋如下图形式:
* h结点
* x结点
* h和x之间 大于x
*/
public Node rotateLeft(Node h) {
Node x = h.right;// 获得当前结点的右子结点
// 下面开始左旋
h.right = x.left;
x.left = h;
x.color = h.color;
h.color = RED;
return x;// 返回左旋后的根
}
/* 右旋如下图形式:
* h结点
* x结点
* 小于x x和h之间
*/
public Node rotateRight(Node h) {
Node x = h.left;// 获得当前结点的左子结点
// 下面开始右旋
h.left = x.right;
x.right = h;
x.color = h.color;
h.color = RED;
return x;// 返回右旋后的根
}
/* 颜色反转(右旋之后)
* 结点h
* hLeft hRight【当前结点的左右孩子的color都为黑链接;并且当前结点的color为红】
*/
public void flipColors(Node h) {
h.left.color = BLACK;
h.right.color = BLACK;
h.color = RED;
}
/*
* 向以node为根的BST插入结点
*/
public Node insert(Node node, int value) {
if (node == null) {
nodeCount++;
return new Node(value, null, null, RED);
}
// 插入结点
if (node.value > value) {
node.left = insert(node.left, value);
} else {
node.right = insert(node.right, value);
}
// 插入结点后可能引起不平衡
// 1、左旋
if (isRed(node.right) && !isRed(node.left)) {
node = rotateLeft(node);
}
// 2、右旋
if (isRed(node.left) && isRed(node.left.left)) {
node = rotateRight(node);
}
// 3、颜色反转
if (isRed(node.left) && isRed(node.right)) {
flipColors(node);
}
return node;// 返回插入后新BST的根结点
}
// 向红黑树中插入结点
public void add(int value) {
root = insert(root, value);
root.color = BLACK;//根结点总是黑=
}
// 中序遍历红黑树
public void search(Node node) {
if (node == null) {
return;
}
search(node.left);
System.out.print(node.value + " ");
search(node.right);
}
public static void main(String[] args) {
RedBlackTree tree = new RedBlackTree();
tree.add(2);
tree.add(3);
tree.add(4);
tree.add(5);
tree.add(8);
tree.add(7);
System.out.println("红黑树的结点个数--->" + tree.size());
System.out.println("红黑树的根结点的值--->" + tree.root.value);
System.out.println("-------中序遍历红黑树------");
tree.search(tree.root);
}
}
2-3-4树和红黑树
2-3-4树只是在2-3树的基础上进行了扩展,也是一棵自平衡的多路查找树(B树),插入删除操作与2-3树类似。
对应的红黑树相比于左倾红黑树,其红链接也可以为右链接。注意:一棵2-3-4树可以对应多个红黑树;但是一个红黑树只能对应一个2-3-4树。
PS:下面这种也对,但是上面更符合2-3树对应的红黑树的习惯。
红黑树和AVL树的比较
二者都是平衡树,都避免了二叉查找树退化为线性表。那么为什么还需要红黑树呢?直接使用AVL树不就行了吗?其实主要还是插入删除情况下,红黑树的效率更高。
AVL 树是高度平衡的,频繁的插入和删除,会引起频繁的reblance,导致效率下降。
红黑树不是高度平衡的,算是一种折中,插入最多两次旋转,删除最多三次旋转
2-3树等多路查找树和AVL树的比较
都是O(logN)级别查询效率,但是面对大数据量情况下,AVL树高度太大,相比下多路查找树高度会降低很多。因此多路查找树(B树)特别适合大数据量查找。