数据结构-红黑树和2-3树

一. 红黑树的定义

  • 1.每个节点或者是红色的,或者是黑色的
  • 2.根节点是黑色的(一棵空树也是红黑树)。
  • 3.每个叶子节点(最后的空节点)是黑色的,定义空节点都是黑色的。
  • 4.如果一个节点是红色的,那么他的孩子节点都是黑色的。
  • 5.从任意一个节点到叶子节点,经过的黑色节点是一样的。

学习红黑树之前,必须先学2-3树

二. 2-3树

学习2-3树,不仅对于理解红黑树有帮助,对于B树的数据结构也有帮助。

  • 2-3 树满足二分搜索树的基本性质,2-3树不是二叉树。
  • 节点可以存放一个元素活着2个元素。
  • 2-3树是一颗绝对平衡的树:任何一个节点左右子树的高度一定是相同的。
    数据结构-红黑树和2-3树_第1张图片

如何维护2-3的平衡

数据结构-红黑树和2-3树_第2张图片

三. 红黑树和2-3树的等价性

数据结构-红黑树和2-3树_第3张图片
数据结构-红黑树和2-3树_第4张图片
红黑树是保持“黑”平衡的二叉树,严格意义上,不是平衡二叉树,最大高度为2logN O(logN)
AVL的高度logN. 比AVL树的高度高,查询的时候比AVL慢一点. 如果只查询的时候,AVL快一点。

五. 红黑树添加新元素

2-3树中添加一个新元素: 或者添加晋2-节点,形成3-节点,或者添加进3-节点,暂时形成一个4-节点。
永远添加红色节点。然后维护红黑树。
添加节点之后,保持最终根节点的颜色为黑色。

红黑树的左旋转 、右旋转、颜色翻转

数据结构-红黑树和2-3树_第5张图片
数据结构-红黑树和2-3树_第6张图片
数据结构-红黑树和2-3树_第7张图片

红黑树的添加

数据结构-红黑树和2-3树_第8张图片

总结的流程如下:维护的时机和AVL树一样,添加节点后回溯向上维护。
数据结构-红黑树和2-3树_第9张图片

六. 红黑树的Java简单实现

package com.mk.coffee.test.dataStructure.redblackTree;

/**
 * 红黑树 《算法导论》:
 * 

* 1.每个节点或者是红色的,或者是黑色的 * 2.根节点是黑色的(一棵空树也是红黑树)。 * 3.每个叶子节点(最后的空节点)是黑色的,定义空节点都是黑色的。 * 4.如果一个节点是红色的,那么他的孩子节点都是黑色的。连接的可能2/3节点,都是黑色的。 * 5.从任意一个节点到叶子节点,经过的黑色节点是一样的。(2-3树绝对平衡,经过的节点个数相同) * 黑色节点的右孩子节点一定是黑色的。 *

* 《算法4》 红黑树的发明人 Robert Sedgewick 是 Donald Knuth的学生。 *

* 红黑树与 2-3 树的等价性 * 理解2-3树和红黑树之间的关系。 * 红色节点都是左孩子 * 红黑树是保持"黑平衡"的二叉树,严格意义来说不是平衡二叉树。 * 节点数 为N,最大高度2logN,O(logN) * 红黑树比AVL 查找慢一点 ,红黑树2logN, AVLlogN *

* 添加的情况下,RBT好 AVL 28S RBT20S * 查询的情况下:AVL好 *

* Java java.util.中的 TreeMap 和 TreeSet 基于红黑树 * 红黑树的其他实现。 * 总结: * 对于完全随机的数据,普通的二分搜索树就很好用! * 确定:极端的情况退化成链表(或者高度不平衡) * 对于查询较多的情况下,AVL树很好用! * 红黑树牺牲了平衡性(2logN的高度) * 统计性能更优(综合增删改查所有的操作,平均性能,红黑树性能最高) *

*

* 更多的问提 *

*

* 伸展树Splay Tree:局部性原理: 刚被访问的内容下次高概率被再次访问。 * * @author makui * @date 2020/03/26 */ public class RedBlackTree<K extends Comparable<K>, V> { //RED BLACk 代表红黑色 private static final boolean RED = true; private static final boolean BLACK = false; private class Node { public K key; public V value; public Node left, right; public boolean color; public Node(K key, V value) { this.key = key; this.value = value; this.left = null; this.right = null; //默认是红色 等价2-3树添加节点永远是先和节点融合,然后在做操作 color = RED; } } private Node root; private int size; public RedBlackTree() { root = null; size = 0; } //传入的Node左旋转 返回旋转后的根节点 //左旋转 // T1 < y < T2 < x < T3 < z < T4 private Node leftRotate(Node node) { Node x = node.right; //左旋转 node.right = x.left; x.left = node; x.color = node.color; node.color = RED; return x; } private Node rightRotate(Node node) { Node x = node.left; //右旋转 node.left = x.right; x.right = node; x.color = node.color; node.color = RED; return x; } /** * 向红黑树添加新的元素(key,value) * 保持根节点的颜色为黑色 * * @param key * @param value */ public void add(K key, V value) { root = add(root, key, value); root.color = BLACK; } /** * 向node为根的红黑色中添加元素(key,value),递归算法 * 返回插入新节点后红黑树的根 * * @param node * @param key * @param value * @return */ private Node add(Node node, K key, V value) { if (node == null) { size++; return new Node(key, value); //默认插入红色节点 } if (key.compareTo(node.key) < 0) { node.left = add(node.left, key, value); } else if (key.compareTo(node.key) > 0) { node.right = add(node.right, key, value); } else node.value = value; //右节点是红色的,左节点是黑色的,左旋转 如果都是红色,就是颜色翻转 if (isRed(node.right) && !isRed(node.left)) { node = leftRotate(node); } //左节点是红的的,左孩子的左孩子是红色的,连续两个红节点,右旋转 if (isRed(node.left) && isRed(node.left.left)) { node = rightRotate(node); } //左右节点都是红节点,需要颜色翻转 if (isRed(node.left) && isRed(node.right)) { //转换颜色 flipColor(node); } return node; } private void flipColor(Node node) { node.color = true; node.left.color = false; node.right.color = false; } private boolean isRed(Node node) { return node.color; } /** * 返回以node为根节点的二分搜索树中,key所在的节点 * * @param node * @param key * @return */ private Node getNode(Node node, K key) { if (node == null) { return null; } if (key.compareTo(node.key) == 0) { return node; } else if (key.compareTo(node.key) < 0) { return getNode(node.left, key); } else { return getNode(node.right, key); } } public V remove(K key) { //todo 仿照BST 自己写 return null; } public boolean contains(K key) { return getNode(root, key) != null; } public V get(K key) { Node node = getNode(root, key); return node == null ? null : node.value; } public void set(K key, V newValue) { Node node = getNode(root, key); if (node == null) { throw new IllegalArgumentException(key + "doesn't exist~"); } node.value = newValue; } /** * 查找二分查找树的最小元素 * * @return */ public V minimum() { if (size == 0) { throw new IllegalArgumentException("BST is empty!"); } return minimum(root).value; } /** * 最小值以node为根的二分搜索树 * * @param node * @return */ private Node minimum(Node node) { if (node.left == null) { return node; } return minimum(node.left); } /** * 查找二分查找树的最大元素 * * @return */ public V maximum() { if (size == 0) { throw new IllegalArgumentException("BST is empty!"); } return maximum(root).value; } /** * 最大值以node为根的二分搜索树 * * @param node * @return */ private Node maximum(Node node) { if (node.right == null) { return node; } return minimum(node.right); } public int getSize() { return size; } public boolean isEmpty() { return size == 0; } }

七. 红黑树和AVL对比

如果插入一个node引起了树的不平衡,AVL和RB-Tree都是最多只需要2次旋转操作,即两者都是O(1);但是在删除node引起树的不平衡时,最坏情况下,AVL需要维护从被删node到root这条路径上所有node的平衡性,因此需要旋转的量级O(logN),而RB-Tree最多只需3次旋转(左旋转,右旋转,颜色翻转),只需要O(1)的复杂度。
其次,AVL的结构相较RB-Tree来说更为平衡,在插入和删除node更容易引起Tree的unbalance,因此在大量数据需要插入或者删除时,AVL需要rebalance的频率会更高。因此,RB-Tree在需要大量插入和删除node的场景下,效率更高。自然,由于AVL高度平衡,因此AVL的search效率更高。
map的实现只是折衷了两者在search、insert以及delete下的效率。总体来说,RB-tree的统计性能是高于AVL的。

你可能感兴趣的:(数据结构,数据结构,树结构)