TreeMap

TreeMap

TreeMap 的实现是红黑树算法的实现,因此要了解 TreeMap 就必须对红黑树有必定的了解。html

其实这篇博文的名字叫作:根据红黑树的算法来分析 TreeMap 的实现,可是为了与 Java 提升篇系列博文保持一致仍是叫作 TreeMap 比较好。经过这篇博文你能够得到以下知识点:java

  • 一、红黑树的基本概念。算法

  • 二、红黑树增长节点、删除节点的实现过程。bash

  • 三、红黑树左旋转、右旋转的复杂过程。数据结构

  • 四、Java 中 TreeMap 是如何经过 put、deleteEntry 两个来实现红黑树增长、删除节点的。spa

我想经过这篇博文你对 TreeMap 必定有了更深的认识。好了,下面先简单普及红黑树知识。.net

红黑树简介

红黑树又称红-黑二叉树,它首先是一颗二叉树,它具体二叉树全部的特性。同时红黑树更是一颗自平衡的排序二叉树。code

咱们知道一颗基本的二叉树他们都须要知足一个基本性质–即树中的任何节点的值大于它的左子节点,且小于它的右子节点。cdn

按照这个基本性质使得树的检索效率大大提升。咱们知道在生成二叉树的过程是很是容易失衡的,最坏的状况就是一边倒(只有右/左子树),这样势必会致使二叉树的检索效率大大下降(O(n)),因此为了维持二叉树的平衡,大牛们提出了各类实现的算法,如:AVL,SBT,伸展树,TREAP ,红黑树等等。htm

平衡二叉树必须具有以下特性:它是一棵空树或它的左右两个子树的高度差的绝对值不超过 1,而且左右两个子树都是一棵平衡二叉树。也就是说该二叉树的任何一个等等子节点,其左右子树的高度都相近。

红黑树顾名思义就是节点是红色或者黑色的平衡二叉树,它经过颜色的约束来维持着二叉树的平衡。对于一棵有效的红黑树二叉树而言咱们必须增长以下规则:

  • 一、每一个节点都只能是红色或者黑色

  • 二、根节点是黑色

  • 三、每一个叶节点(NIL 节点,空节点)是黑色的。

  • 四、若是一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。

  • 五、从任一节点到其每一个叶子的全部路径都包含相同数目的黑色节点。

这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径很少于最短的可能路径的两倍长。结果是这棵树大体上是平衡的。由于操做好比插入、删除和查找某个值的最坏状况时间都要求与树的高度成比例,这个在高度上的理论上限容许红黑树在最坏状况下都是高效的,而不一样于普通的二叉查找树。因此红黑树它是复杂而高效的,其检索效率 O(log n)。下图为一颗典型的红黑二叉树。

对于红黑二叉树而言它主要包括三大基本操做:左旋、右旋、着色。

注:因为本文主要是讲解 Java 中 TreeMap,因此并无对红黑树进行很是深刻的了解和研究,若是诸位想对其进行更加深刻的研究Lz提供几篇较好的博文:

一、红黑树系列集锦

二、红黑树数据结构剖析

三、红黑树

2、TreeMap 数据结构

public class TreeMap<K,V>
        extends AbstractMap<K,V>
        implements NavigableMap<K,V>, Cloneable, java.io.Serializable
复制代码

TreeMap 继承 AbstractMap,实现 NavigableMap、Cloneable、Serializable 三个接口。其中 AbstractMap 代表 TreeMap 为一个 Map 即支持 key-value 的集合,NavigableMap(更多)则意味着它支持一系列的导航方法,具有针对给定搜索目标返回最接近匹配项的导航方法 。

TreeMap 中同时也包含了以下几个重要的属性:

//比较器,由于TreeMap是有序的,经过comparator接口咱们能够对TreeMap的内部排序进行精密的控制
        private final Comparator<? super K> comparator;
            //TreeMap红-黑节点,为TreeMap的内部类
        private transient Entry<K,V> root = null;
            //容器大小
        private transient int size = 0;
            //TreeMap修改次数
        private transient int modCount = 0;
            //红黑树的节点颜色--红色
        private static final boolean RED = false;
            //红黑树的节点颜色--黑色
        private static final boolean BLACK = true;
复制代码

对于叶子节点 Entry 是 TreeMap 的内部类,它有几个重要的属性:

//键
            K key;
            //值
            V value;
            //左孩子
            Entry<K,V> left = null;
            //右孩子
            Entry<K,V> right = null;
            //父亲
            Entry<K,V> parent;
            //颜色
            boolean color = BLACK;
复制代码

注:前面只是开胃菜,下面是本篇博文的重中之重,在下面两节我将重点讲解 treeMap 的 put()、delete() 方法。经过这两个方法咱们会了解红黑树增长、删除节点的核心算法。

TreeMap put() 方法

在了解 TreeMap 的 put() 方法以前,咱们先了解红黑树增长节点的算法。

红黑树增长节点

红黑树在新增节点过程当中比较复杂,复杂归复杂它一样必需要依据上面提到的五点规范,同时因为规则 一、二、3 基本都会知足,下面咱们主要讨论规则 四、5。假设咱们这里有一棵最简单的树,咱们规定新增的节点为 N、它的父节点为 P、P 的兄弟节点为 U、P 的父节点为 G。

新增N 父节点P P的兄弟U P的父节点G

  • 插入新节点总为红色
  • 若是插入节点的父节点是黑色,能维持性质
  • 若是插入节点的父节点是红色,破坏了性质。插入算法着色或旋转
public V put(K key, V value) {
                //用t表示二叉树的当前节点
                Entry<K,V> t = root;
                //t为null表示一个空树,即TreeMap中没有任何元素,直接插入
                if (t == null) {
                    compare(key, key); // type (and possibly null) check //将新的key-value键值对建立为一个Entry节点,并将该节点赋予给root
                    root = new Entry<>(key, value, null);
                    //容器的size = 1,表示TreeMap集合中存在一 个元素
                    size = 1;
                    //修改次数 + 1
                    modCount++;
                    return null;
                }
                //cmp表示key排序的返回结果
                int cmp;    
                //父节点
                Entry<K,V> parent; 
                // split comparator and comparable paths
                Comparator<? super K> cpr = comparator;    //指定的排序算法
                //若是cpr不为空,则采用既定的排序算法进行建立TreeMap集合
                if (cpr != null) {
                    //排序二叉树
                    do {
                        parent = t;      //parent指向上次循环后的t
                        //比较新增节点的key和当前节点key的大小
                        cmp = cpr.compare(key, t.key);
                        //cmp返回值小于0,表示新增节点的key小于当前节点的key,则以当前节点的左子节点做为新的当前节点
                        if (cmp < 0)
                            t = t.left;
                        //cmp返回值大于0,表示新增节点的key大于当前节点的key,则以当前节点的右子节点做为新的当前节点
                        else if (cmp > 0)
                            t = t.right;
                        //cmp返回值等于0,表示两个key值相等,则新值覆盖旧值,并返回新值
                        else
                            return t.setValue(value);
                    } while (t != null);
                }
                //若是cpr为空,则采用默认的排序算法进行建立 TreeMap集合
                else {
                    if (key == null)     //key值为空抛出异常
                        throw new NullPointerException();
                    /* 下面处理过程和上面同样 */
                    Comparable<? super K> k =   (Comparable<? super K>) key;
                    do {
                        parent = t;
                        cmp = k.compareTo(t.key);
                        if (cmp < 0)
                            t = t.left;
                        else if (cmp > 0)
                            t = t.right;
                        else
                           return t.setValue(value);
                    } while (t != null);
                }
                //将新增节点当作parent的子节点
                Entry<K,V> e = new Entry<>(key, value,   parent);
                //若是新增节点的key小于parent的key,则当作左子节点
                if (cmp < 0)
                    parent.left = e;
                //若是新增节点的key大于parent的key,则当作右子节点
                else
                    parent.right = e;
                /* * 上面已经完成了排序二叉树的的构建,将新增节点 插入该树中的合适位置 * 下面fixAfterInsertion()方法就是对这棵树进行调整、平衡,具体过程参考上面的五种状况 */
                fixAfterInsertion(e);
                //TreeMap元素数量 + 1
                size++;
                //TreeMap容器修改次数 + 1
                modCount++;
                return null;
            }
复制代码

do{} 代码块是实现排序二叉树的核心算法,经过该算法咱们能够确认新增节点在该树的正确位置。

红黑树是一棵平衡排序二叉树,普通的排序二叉树可能会出现失衡的状况,因此下一步就是要进行调整。fixAfterInsertion(e); 调整的过程务必会涉及到红黑树的左旋、右旋、着色三个基本操做。代码以下:

/** * 新增节点后的修复操做 * x 表示新增节点 */
         private void fixAfterInsertion(Entry<K,V> x) {
                x.color = RED;   //新增节点的颜色为红色
                //循环 直到 x不是根节点,且x的父节点不为红色
                while (x != null && x != root && x.parent.color == RED) {
      //若是X的父节点(P)是其父节点的父节点(G)的左节点
                    if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                        //获取X的叔节点(U)
                        Entry<K,V> y = rightOf(parentOf (parentOf(x)));
                        //若是X的叔节点(U) 为红色(状况三)
                        if (colorOf(y) == RED) {     
                            //将X的父节点(P)设置为黑色
                            setColor(parentOf(x), BLACK);
                            //将X的叔节点(U)设置为黑色
                            setColor(y, BLACK);
                            //将X的父节点的父节点(G)设置红色
                            setColor(parentOf(parentOf(x)), RED);
                            x = parentOf(parentOf(x));
                        }
                        //若是X的叔节点(U为黑色);这里会存在 两种状况(状况4、状况五)
                        else {   
                            //若是X节点为其父节点(P)的右子 树,则进行左旋转(状况四)
                            if (x == rightOf(parentOf(x))) {
                                //将X的父节点做为X
                                x = parentOf(x);
                                //右旋转
                                rotateLeft(x);
                            }
                            //(状况五)
                            //将X的父节点(P)设置为黑色
                            setColor(parentOf(x), BLACK);
                            //将X的父节点的父节点(G)设置红色
                            setColor(parentOf(parentOf(x)), RED);                            //以X的父节点的父节点(G)为中心右旋转
                           rotateRight(parentOf(parentOf(x)));
                        }
                    }                
                    //若是X的父节点(P)是其父节点的父节点(G的右节点)
                    else {
                        //获取X的叔节点(U)
                        Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                        //若是X的叔节点(U) 为红色(状况三)
                        if (colorOf(y) == RED) {
                            //将P、U设为黑色,G设为黑色
                            //将X的父节点(P)设置为黑色
                            setColor(parentOf(x), BLACK);
                            //将X的叔节点(U)设置为黑色
                            setColor(y, BLACK);
                            //将X的父节点的父节点(G)设置红色
                            setColor(parentOf(parentOf(x)), RED);
                            //
                            x = parentOf(parentOf(x));
                        }
                      //若是X的叔节点(U为黑色);这里会存在两种状况(状况4、状况五)
                        else {
                            //若是X节点为其父节点(P)的右子树,则进行左旋转(状况四)
                            if (x == leftOf(parentOf(x)))   {
                                //将X的父节点做为X
                                x = parentOf(x);
                                //右旋转
                                rotateRight(x);
                            }
                            //(状况五)
                            //将X的父节点(P)设置为黑色
                            setColor(parentOf(x), BLACK);
                            //将X的父节点的父节点(G)设置红色
                            setColor(parentOf(parentOf(x)), RED);
                            //以X的父节点的父节点(G)为中心右旋转
                            rotateLeft(parentOf(parentOf(x)));
                        }
                    }
                }
                //将根节点G强制设置为黑色
                root.color = BLACK;
            }
复制代码
相关文章
相关标签/搜索