一 简介
1、概念
TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
TreeMap 类不仅实现了 Map 接口,还实现了 Map 接口的子接口 java.util.SortedMap。
TreeMap 类中不允许键对象为 null 或是 基本数据类型,这是因为 TreeMap 中的对象必须是可排序的(即对象需要实现 java.lang.Comparable 接口)
2、成员变量
//比较器,因为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;
3、构造方法
public TreeMap();
public TreeMap(Comparator<? super K> comparator);
public TreeMap(Map<? extends K, ? extends V> m);
public TreeMap(SortedMap<K, ? extends V> m);
4、成员方法
public Comparator<? super K> comparator();
//获取 TreeMap 实例使用的 Comparator。使用空的构造方法创建的 TreeMap 实例,则返回 null
public K firstKey();
//获取第一个(排在最低的)对象的 Key
public K lastKey();
//获取最后个(排在最高的)对象的 Key
public SortedMap<K,V> headMap(K toKey);
//获取一个子集。其所有对象的 key 的值小于 toKey
public SortedMap<K,V> subMap(K fromKey, K toKey);
//获取一个子集。其所有对象的 key 的值小于 toKey ,大于等于 fromKey
public SortedMap<K,V> tailMap(K fromKey);
//获取一个子集。其所有对象的 key 的值大于等于 fromKey
5、算法实现
TreeMap的实现是红黑树算法的实现,红黑树又称红黑二叉树,它首先是一颗二叉树,它具备二叉树所有的特性。同时红黑树更是一颗自平衡的排序二叉树。
红黑树具有如下规则:
1.每个节点都只能是红色或者黑色
2.根节点是黑色
3.每个叶节点是黑色的
4.如果一个节点是红的,则它两个子节点都是黑的,在一条路径不能出现相邻的两个红色节点
5.从任一节点到每个叶子的所有路径都包含相同数据的黑色节点
这些约束强制了红黑树的关键性质:从根到叶子的最长路径不多于最短的可能路径的两倍长。结果是这棵树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的。红黑树的检索效率为O(log n)。
二 源码解析
1、put操作
public V put(K key, V value) {
//用t表示二叉树的当前节点
Entry<K,V> t = root;
//t为null表示一个空树,即TreeMap中没有任何元素,直接插入
if (t == null) {
//比较key值,个人觉得这句代码没有任何意义,空树还需要比较、排序?
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;
}
int cmp; //cmp表示key排序的返回结果
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; //parent指向上次循环后的t
//比较新增节点的key和当前节点key的大小
cmp = k.compareTo(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);
}
//当Map不为null且不存在新插入的key时,将新增节点当做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(e);
//元素数量 + 1
size++;
//修改次数 + 1
modCount++;
return null;
}
2、新增节点红黑树的调整情况
1.父节点为空
若插入的节点N没有父节点,则直接当做根节点插入即可,同时将颜色设置为黑色。
2.父节点为黑色
这种情况新节点同样是直接插入,同时颜色为红色,由于根据规则4它会存在两个黑色的叶子节点,值为null。同时由于新增节点N为红色,所以通过它的子节点的路径依然会保存着相同的黑色节点数,同样满足规则5
3.父节点为红色,且叔父节点为红色
对于这种情况若直接插入肯定会出现不平衡现象。策略为将父节点和叔父节点变黑、祖父节点变红。这时由于经过父节点、叔父节点的路径都必须经过G所以在这些路径上面的黑节点数目还是相同的。但是经过上面的处理,可能祖父节点的父节点也是红色,这个时候我们需要将祖父节点当做新增节点做递归处理。
4.父节点为红色,叔父节点为黑色或空,且新增节点为右孩子
这种情况对新增节点和其父节点进行一次左旋转,
5.父节点为红色,叔父节点为黑色或空,且新增节点为左孩子
3、fixAfterInsertion操作
TreeMap的底层实现是红黑树,红黑树是一棵平衡排序二叉树,普通的排序二叉树可能会出现失衡的情况。fixAfterInsertion()操作就是对插入后的排序二叉树进行调整,使其最终成为红黑树。调整的过程会涉及到红黑树的左旋、右旋、着色三个基本操作。
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED; //新增节点的颜色为红色
//循环 直到 x不是根节点,且x的父节点为红色
while (x != null && x != root && x.parent.color == RED) {
//如果X的父节点是其祖父节点的左节点
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
//获取X的叔节点(U)
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
//如果X的叔节点(U) 为红色(情况三)
if (colorOf(y) == RED) {
//将X的父节点设置为黑色
setColor(parentOf(x), BLACK);
//将X的叔节点设置为黑色
setColor(y, BLACK);
//将X的祖父节点设置红色
setColor(parentOf(parentOf(x)), RED);
//将x的祖父节点指向x,继续递归
x = parentOf(parentOf(x));
}
//如果X的叔节点(U为黑色);这里会存在两种情况(情况四、情况五)
else {
//如果X节点为其父节点的右子树,则进行左旋转(情况四)
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
//(情况五)
//将X的父节点设置为黑色
setColor(parentOf(x), BLACK);
//将X的祖父节点设置红色
setColor(parentOf(parentOf(x)), RED);
//以X的祖父节点为中心右旋转
rotateRight(parentOf(parentOf(x)));
}
}
//如果X的父节点是其祖父节点的右节点
else {
//获取X的叔节点(U)
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
//如果X的叔节点为红色(情况三)
if (colorOf(y) == RED) {
//将X的父节点设置为黑色
setColor(parentOf(x), BLACK);
//将X的叔节点设置为黑色
setColor(y, BLACK);
//将X的祖父节点设置红色
setColor(parentOf(parentOf(x)), RED);
//将x的祖父节点指向x,继续递归
x = parentOf(parentOf(x));
}
//如果X的叔节点为黑色;这里会存在两种情况(情况四、情况五)
else {
//如果X节点为其父节点左右子树,则进行右旋转(情况四)
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
//(情况五)
//将X的父节点设置为黑色
setColor(parentOf(x), BLACK);
//将X的祖父节点设置红色
setColor(parentOf(parentOf(x)), RED);
//以X的祖父节点为中心左旋转
rotateLeft(parentOf(parentOf(x)));
}
}
}
//将根节点G强制设置为黑色
root.color = BLACK;
}
4、rotateRight(右旋)操作
private void rotateRight(Entry<K,V> p) {
if (p != null) {
Entry<K,V> l = p.left;
p.left = l.right;
if (l.right != null) l.right.parent = p;
l.parent = p.parent;
if (p.parent == null)
root = l;
else if (p.parent.right == p)
p.parent.right = l;
else p.parent.left = l;
l.right = p;
p.parent = l;
}
}
5、rotateLeft(左旋)操作
private void rotateLeft(Entry<K,V> p) {
if (p != null) {
Entry<K,V> r = p.right;
p.right = r.left;
if (r.left != null)
r.left.parent = p;
r.parent = p.parent;
if (p.parent == null)
root = r;
else if (p.parent.left == p)
p.parent.left = r;
else
p.parent.right = r;
r.left = p;
p.parent = r;
}
}
6、deleteEntry操作
deleteEntry操作是删除红黑树中节点的操作。由于红黑树是一颗增强版的二叉查找树,红黑树的删除操作跟普通的二叉查找树的删除操作也就非常相似,唯一的区别是由于删除操作会改变红黑树的结构,可能破坏红黑树的约束条件,因此有可能要进行调整。
一颗普通的二叉查找数删除的过程可分为:
1.删除点p的左右子树都为空,或者只有一颗子树为空
2.删除点p的左右子树都非空
对于情况1,直接将p删除(左右子树都为空时),或者用非空子树替代p(只有一颗树非空时)。对于情况2,可以用p的后继节点 s(树种大于x的最小的那个元素) 代替p,然后使用情况1删除s。即如果p符合情况1,删除p,否则p符合情况2,找出其后继节点s,此时s必定会符合情况1,将s的key与value赋值给p,按情况1删除s
private void deleteEntry(Entry<K,V> p) {
modCount++;
size--;
// If strictly internal, copy successor's element to p and then make p
// point to successor.
if (p.left != null && p.right != null) { //删除点p的左右子树都非空时,用后继节点s来代替p
//求p节点的后继节点
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
} // p has 2 children
// Start fixup at replacement node, if it exists.
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) { //删除点p左右子树不都为空,此时p为原p节点的后继节点s
// Link replacement to parent
replacement.parent = p.parent;
//用replacement来替代p节点
if (p.parent == null)
root = replacement;
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
// 将P节点从这棵树中剔除掉
p.left = p.right = p.parent = null;
//若P为红色直接删除,红黑树保持平衡;但是若P为黑色,则需要调整红黑树使其保持平衡
// Fix replacement
if (p.color == BLACK)
fixAfterDeletion(replacement);
} else if (p.parent == null) { //删除点p的左右子树都为空,且没有父节点,表示为P根节点,直接删除即可
root = null;
} else { //删除点p的左右子树都为空,但有父节点
//如果P节点的颜色为黑色,对红黑树进行调整
if (p.color == BLACK)
fixAfterDeletion(p);
//删除P节点
if (p.parent != null) {
if (p == p.parent.left)
p.parent.left = null;
else if (p == p.parent.right)
p.parent.right = null;
p.parent = null;
}
}
}
7、successor操作
successor操作是当删除节点p左右子树都不为空时,用来寻找p的后继节点
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
if (t == null)
return null;
//t的右子树不为空,则t的后继是其右子树中最小的那个元素
else if (t.right != null) {
Entry<K,V> p = t.right;
while (p.left != null)
p = p.left;
return p;
}
//t的右子树为空,则t的后继是其第一个向左走的祖先
else {
Entry<K,V> p = t.parent;
Entry<K,V> ch = t;
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p;
}
}
8、删除节点时红黑树调整情况
1、兄弟节点为红色
2、兄弟节点为黑色,且兄弟节点的两个孩子都是黑色
3、兄弟节点为黑色,且兄弟节点的左孩子是红色,右孩子是黑色
4、兄弟节点为黑色,且兄弟节点的右孩子是红色
9、fixAfterDeletion(删除后平衡)操作
private void fixAfterDeletion(Entry<K,V> x) {
while (x != root && colorOf(x) == BLACK) {
if (x == leftOf(parentOf(x))) { //若x节点为左节点
//获取其兄弟节点
Entry<K,V> sib = rightOf(parentOf(x));
/*
* 如果兄弟节点为红色----(情况1)
* 策略:将兄弟节点变为黑色,其父节点变为红色,然后进行一次左旋转
*/
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateLeft(parentOf(x));
sib = rightOf(parentOf(x));
}
/*
* 若兄弟节点的两个子节点都为黑色----(情况2)
* 策略:将兄弟节点变成红色
*/
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
/*
* 如果兄弟节点只有右子树为黑色----(情况3)
* 策略:将兄弟节点与其左子树进行颜色互换然后进行右转
* 这时情况会转变为3.4
*/
if (colorOf(rightOf(sib)) == BLACK) {
setColor(leftOf(sib), BLACK);
setColor(sib, RED);
rotateRight(sib);
sib = rightOf(parentOf(x));
}
/*
*----情况4
*策略:交换兄弟节点和父节点的颜色,
*同时将兄弟节点右子树设置为黑色,最后左旋转
*/
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(rightOf(sib), BLACK);
rotateLeft(parentOf(x));
x = root;
}
}
//x 为右节点,与左节点相似
else { // symmetric
Entry<K,V> sib = leftOf(parentOf(x));
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateRight(parentOf(x));
sib = leftOf(parentOf(x));
}
if (colorOf(rightOf(sib)) == BLACK &&
colorOf(leftOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
if (colorOf(leftOf(sib)) == BLACK) {
setColor(rightOf(sib), BLACK);
setColor(sib, RED);
rotateLeft(sib);
sib = leftOf(parentOf(x));
}
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(leftOf(sib), BLACK);
rotateRight(parentOf(x));
x = root;
}
}
}
setColor(x, BLACK);
}
三 实际应用
1、自定义TreeMap的排序顺序
1)定义比较器
static class MyComparator implements Comparator{
@Override
public int compare(Object o1, Object o2) {
// TODO Auto-generated method stub
String param1 = (String)o1;
String param2 = (String)o2;
return -param1.compareTo(param2);
}
}
2)初始化TreeMap
MyComparator comparator = new MyComparator();
Map<String,String> map = new TreeMap<String,String>(comparator);
2、遍历顺序
1)遍历TreeMap的键值对(entrySet())
Map<String,Object> map = new TreeMap<String,Object>();
map.put("liming", "50");
map.put("zhangsan", "10");
map.put("keven", "30");
map.put("steven", "100");
for(Map.Entry<String, Object> entry:map.entrySet())
System.out.println(entry);
2)遍历TreeMap的键(keySet())
Map<String,Object> map = new TreeMap<String,Object>();
map.put("liming", "50");
map.put("zhangsan", "10");
map.put("keven", "30");
map.put("steven", "100");
for(String key:map.keySet())
System.out.println(map.get(key));
3)遍历TreeMap的值(values())
Map<String,Object> map = new TreeMap<String,Object>();
map.put("liming", "50");
map.put("zhangsan", "10");
map.put("keven", "30");
map.put("steven", "100");
for(Object value:map.values())
System.out.println(value);
3、逆序遍历
1)逆序map
首先根据descendingMap()方法获得逆序排列的map,然后根据顺序遍历的方式遍历
Map<String,Object> map = new TreeMap<String,Object>();
map.put("liming", "50");
map.put("zhangsan", "10");
map.put("keven", "30");
map.put("steven", "100");
NavigableMap<String, Object> map1 = map.descendingMap();
for(Map.Entry<String, Object> entry:map1.entrySet())
System.out.println(entry);
2)逆序key
根据descendingKeySet()方法获取逆序的key集合,然后遍历。其实内部实现还是先获取逆序排列的map
TreeMap<String,Object> map = new TreeMap<String,Object>();
map.put("liming", "50");
map.put("zhangsan", "10");
map.put("keven", "30");
map.put("steven", "100");
for(String key:map.descendingKeySet())
System.out.println(map.get(key));
参考资料:http://blog.csdn.net/chenssy/article/details/26668941
http://www.cnblogs.com/CarpenterLee/p/5525688.html
http://blog.csdn.net/it_zjyang/article/details/51867143