Python微信订餐小程序课程视频
https://edu.csdn.net/course/detail/36074
Python实战量化交易理财系统
https://edu.csdn.net/course/detail/35475
红黑树
定义
动机:
-
二叉查找树查找、插入、删除最坏情况时间复杂度可能退化为
O(n)
。 -
AVL 树很好的限制了数的高度为
O(logn)
,插入、删除、查找的最坏时间复杂度均为O(logn)
;但删除操作最多需要做O(logn)
次旋转。 -
红黑树是具有如下特点的二叉查找树:
- 每个结点是红色或黑色的
- 根结点为黑色
- 外结点为黑色(外界点即为
null
) - 如果一个结点时红色,那么它的孩子必须是黑色
- 任一结点到外结点的路径上,包含相同数目的黑结点
结点的黑高度:该结点到外结点的路径上包含的黑结点数目
红黑树的黑高度:根结点的黑高度
性质
- 若忽略红结点而只考虑黑结点,则这棵树是平衡的
- 任何一条路径上不能有两个连续的红结点。从任意结点触发最长的路径(红黑结点间隔组成)是最短路径(仅由黑结点组成)的
2
倍 - 任何一个结点的左右子树的高度最多相差
2
倍 - 红黑树的平衡性比 AVL 树更弱
- 平均和最坏高度:
O(logn)
- 查找、插入、删除操作的平均和最坏时间复杂度是
O(logn)
,且仅涉及O(1)
次旋转 - 红黑树的高度:
h = O(logn)
且logn <= h <= 2logn
结点定义
1️⃣ key
:关键字的值
2️⃣ value
:关键字的存储信息
3️⃣ parent
:父亲结点的引用
4️⃣ left
:左子树根结点的引用
5️⃣ right
:右子树根结点引用
6️⃣ color
:结点颜色
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
java`class RBNodeextends Comparable, V> {
public RBNode parent;
public RBNode left;
public RBNode right;
public boolean color;
public K key;
public V value;
public RBNode(RBNode parent, K key, V value) {
this.parent = parent;
this.key = key;
this.value = value;
}
}`
插入算法
- 查找,若查找成功则不插入并更新结点值;若查找失败,再查找失败的位置插入新结点
- 新结点总是作为叶结点插入的
- 新结点必须为红色
- 若新结点的父结点是黑色,则插入过程结束
- 若新结点的父结点是红色,则需要处理双红缺陷
这里定义 x
为新插入的结点,p
为 x
的父结点,g
为 x
的爷爷结点,u
为 x
的叔叔
双红修正
1️⃣ x
的叔叔是黑色
g
到x
的路径为LL
型
g
到x
的路径为RR
型
g
到x
的路径为LR
型
g
到x
的路径为RL
型
2️⃣ x
的叔叔是红色
实例
此时结点 50 的父亲结点 60 也为红色,需要进行双红修正。注意到,其叔叔结点 20 也是红色,只需要将父亲结点 60 和叔叔结点 20 变为黑色,再把爷爷结点 30 变为红色,结果如下图。
之后会发现,结点 30 和其父结点 70 都为红色,需要进行双红修正。而结点 30 的叔叔结点 85 为黑色,结点 30 的爷爷结点 15 到达结点 30 的路径是 RL 型,故需要先右旋,再左旋。结果如下图。
旋转操作
由于红黑树结点还拥有 parent
属性,故不能像平衡二叉树一样进行旋转(【数据结构与算法】手撕平衡二叉树),需要特殊考虑 parent
的赋值。
1️⃣ RR
型
左旋
需要特别注意的是爷爷结点 g
的 parent
的设置,需要先判断 g
是左子结点还是右子结点。
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
java private void leftRotate(RBNode g) { if (g != null) { RBNode p = g.right; g.right = p.left; if (p.left != null) { p.left.parent = g; } p.parent = g.parent; if (g.parent == null) { this.root = p; } else if (g.parent.left == g) { g.parent.left = p; } else { g.parent.right = p; } p.left = g; g.parent = p; }
2️⃣ LL
型
右旋
需要特别注意的是爷爷结点 g
的 parent
的设置,需要先判断 g
是左子结点还是右子结点。
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
java private void rightRotate(RBNode g) { if(g != null) { RBNode p = g.left; g.left = p.right; if(p.right != null) { p.right.parent = g; } p.parent = g.parent; if(g.parent == null) { this.root = p; } else if(g.parent.left == g) { g.parent.left = p; } else { g.parent.right = p; } p.right = g; g.parent = p; } }
3️⃣ LR
型
先对 g
的左子结点左旋,再对 g
右旋
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
java private void leftRightRotate(RBNode g) { if(g != null) { leftRotate(g.left); rightRotate(g); } }
4️⃣ RL
型
先对 g
的右子结点右旋,再对 g
左旋
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
java private void rightLeftRotate(RBNode g) { if(g != null) { rightRotate(g.right); leftRotate(g); } }
代码
1️⃣ 插入操作
代码描述
- 如果根结点为空,则创建根结点,返回
- 否则,根据
key
与结点关键值的比较找到插入位置 - 创建结