目录
-
平衡树
-
概念
- 如果我们往一个二叉查找树中依次放入元素7 6 5 4 3 2 1, 那么树的深度就有7层, 这个时候就是最坏情况, 查找的效率依旧很低, 所以我们要引入一些平衡树来解决这种问题
-
-
2-3查找树
-
概念
- 2-结点
- 有两个子结点一个键的结点
- 3-结点
- 有三个子结点两个键的结点
- 2-结点
-
插入
- 如果一个4-结点(里面有三个键)有4个子结点, 那么当它向上提的时候, 需要把剩余的那个3-结点分成两个2-结点
-
2-3树的性质
- 任意空链接到根节点的路径长度都是相等的
- 只有要分解的结点是根结点的时候, 树的高度才会加一
- 2-3查找树和普通二叉查找树的最大区别是, 普通二叉查找树是自顶向下增长, 2-3树是自底向上生长
- 这也就是2-3树是平衡树的原因
-
不做实现
- 因为结点分为2-结点 3-结点, 比较复杂
- 但是这种思想很重要
-
-
红黑树
-
概念
- 基于2-3树的思想实现的
- 红黑树通过左旋 右旋 颜色翻转(平衡二叉树)来降低树的高度, 从而提高查询的效率
- 底层依旧是用标准的二叉查找树(完全由2-结点构成)实现的, 只不过用一些额外的信息(替换3-结点)来表示2-3树
- 红链接: 将两个2-结点连接起来形成3-结点
- 黑链接: 普通的连接
-
红黑树的性质
- 红链接均为左链接
- 没有任何一个结点同时连两个红链接
- 如果同时连了两个那就是4-结点了
- 红黑树是完美黑色平衡的, 即: 任意空链接到根结点的路径上的黑链接的数量相同
- 这正好照应了2-3树上的一个性质: 任意空链接到根结点的路径长度都是相同的
-
红黑树结点API
- color为true代表代表指向自己的链接是红色的
-
平衡化
-
概念
- 对红黑树进行一些增删改的操作之后, 很可能会出现红色的右链接或两条连续的红色链接
-
左旋
- 概念
- 若此结点的左链接为黑色, 右链接为红色, 此时需要左旋
- 概念
-
右旋
- 定义
- 出现两个连续的红色左链接, 需要右旋
- 右旋之后依旧是不符合红黑树的定义的, 因为此时出现了右边的红色链接, 但是我们在右旋的过程中暂时先不解决这个问题, 留到后面使用颜色翻转解决
- 定义
-
-
颜色翻转
-
插入
-
概念
- 插入的时候期望组成3-结点, 所以插入的新节点统统使用红链接, 如果不符合红黑树的定义在进行转换
-
往单个2-结点中插入新键
- 插入的是小值
- 使用红链接插到左边
- 插入的是大值
- 使用红链接插到右边, 然后左旋
- 插入的是小值
-
往一个3-结点插入新键
- 插入的是大值
- 需要用到颜色翻转
- 插入的是小值
- 先用右旋, 再用颜色翻转
- 插入的是中间值
- 先左旋, 再右旋, 最后颜色翻转
- 插入的是大值
-
-
红黑树实现
-
代码
-
package lq.tree; public class RedBlackTree<Key extends Comparable<Key>, Value> { //根节点 private Node root; //记录树中元素的个数 private int N; //红色链接 private static final boolean RED = true; //黑色链接 private static final boolean BLACK = false; //结点类 private class Node { //存储键 public Key key; //存储值 private Value value; //记录左子结点 public Node left; //记录右子结点 public Node right; //由其父结点指向它的链接的颜色 public boolean color; public Node(Key key, Value value, Node left, Node right, boolean color) { this.key = key; this.value = value; this.left = left; this.right = right; this.color = color; } } //获取树中元素的个数 public int size() { return N; } /** * 判断当前节点的父指向链接是否为红色 * * @param x * @return */ private boolean isRed(Node x) { if (x == null) { return BLACK; } return x.color == RED; } /** * 左旋转 * * @param h * @return */ private Node rotateLeft(Node h) { //获取h结点的右子节点, 记为x Node x = h.right; //让h结点的右子节点=x结点的左子节点 h.right = x.left; //让x结点的左子节点=h结点 x.left = h; h.color = x.color; x.color = RED; return x; } /** * 右旋 * * @param h * @return */ private Node rotateRight(Node h) { Node x = h.left; h.left = x.right; x.right = h; x.color = h.color; h.color = RED; return x; } /** * 颜色反转,相当于完成拆分4-节点 * * @param h */ private void flipColors(Node h) { h.color = RED; h.left.color = BLACK; h.right.color = BLACK; } /** * 在整个树上完成插入操作 * * @param key * @param val */ public void put(Key key, Value val) { root = put(root, key, val); root.color = BLACK; } /** * 在指定树中,完成插入操作,并返回添加元素后新的树 * * @param h * @param key * @param val */ private Node put(Node h, Key key, Value val) { if (h == null) { N++; return new Node(key, val, null, null, RED); } int compare = key.compareTo(h.key); if (compare < 0) { h.left = put(h.left, key, val); } else if (compare > 0) { h.right = put(h.right, key, val); } else { h.value = val; } //先判断是否需要左旋 if (isRed(h.right) && !isRed(h.left)) { h = rotateLeft(h); } //再判断是否需要右旋 if (isRed(h.left) && isRed(h.left.left)) { h = rotateRight(h); } //最后判断是否需要颜色翻转 if (isRed(h.left) && isRed(h.right)) { flipColors(h); } return h; } //根据key,从树中找出对应的值 public Value get(Key key) { return get(root, key); } //从指定的树x中,查找key对应的值 public Value get(Node x, Key key) { if (x == null) { return null; } int compare = key.compareTo(x.key); if (compare > 0) { return get(x.right, key); } else if (compare < 0) { return get(x.left, key); } else { return x.value; } } }
-
-
-
-
B-树
-
定义
- B树就是B-树
- 一个节点中, 允许有多可key, 可以有3个 4个 5个 甚至更多, 具体多少不确定
- M阶B树
- 每个结点最多M-1个key
- 最多M个子结点
- 最少两个子结点
- 实际使用中阶数一般大于100
- 存储大量数据后高度依然比较小
-
应用场景
- 磁盘IO
-
-
B+树
-
B+树的应用
-
给数据库中的表建立索引
- 数据库的索引底层是使用B+树实现的
- 非常适合区间查询
-
-