一、红黑树说明
1.红黑树的结构
红黑树是一种特殊的二叉树,它有以下性质:
- 每一个节点或着成红色,或着成红色
- 根是黑色的
- 如果一个节点是红色的,那么它的子节点必须是黑色的(如果这个节点是叶子节点,即没有子节点,不需要满足这个条件,这是在有子节点的情况下)
- 从根节点到叶子节点的每一条路径必须包含相同数目的黑色节点
2.将普通二叉树变成红黑树
- 将节点着色或改变颜色
- 将节点进行旋转操作
二、TreeMap的使用
1.TreeMap与其他集合结构的关系以及它拥有的方法
2.TreeMap的实例化
- new TreeMap() : 直接实例化,采用默认的比较器
- new TreeMap(Comparator comparator):通过一个指定的比较器来实例化
- new TreeMap(Map m) : 通过一个Map对象来实例化
- new TreeMap(SortedMap m): 通过一个SortedMap对象来实例化
TreeMap<Integer, String> tree = new TreeMap<Integer, String>();
Map<Integer, String> bookMap = new HashMap<Integer, String>(2);
bookMap.put(25, "Mybatis");
bookMap.put(36, "Dubbo");
TreeMap<Integer, String> bookTree = new TreeMap<Integer, String>(bookMap);
System.out.println(bookTree); //{25=Mybatis, 36=Dubbo}
3.containsKey:是否包含某个key
- containsKey(Object k)
TreeMap<Integer, String> tree = new TreeMap<Integer, String>();
tree.put(2, "算法");
tree.put(3, "JAVA");
tree.put(1, "数据结构");
tree.put(100, "Docker虚拟化容器");
tree.put(8, "Spring");
tree.put(7, "Zookeeper");
System.out.println(tree.containsKey(3)); // true
System.out.println(tree.containsKey(99)); // false
4.containsValue:是否包含某个值
- containsValue(Object v)
TreeMap<Integer, String> tree = new TreeMap<Integer, String>();
tree.put(2, "算法");
tree.put(3, "JAVA");
System.out.println(tree.containsValue("JAVA")); //true
System.out.println(tree.containsValue("Golang")); //false
5.get:获取某个key的值
TreeMap<Integer, String> tree = new TreeMap<Integer, String>();
tree.put(2, "算法");
tree.put(3, "JAVA");
tree.put(1, "数据结构");
tree.put(100, "Docker虚拟化容器");
tree.put(8, "Spring");
tree.put(7, "Zookeeper");
System.out.println(tree.get(8)); //Spring
System.out.println(tree.get(9)); //null
6.put:设置某个key的值
- put(K key, V value)
TreeMap<Integer, String> tree = new TreeMap<Integer, String>();
tree.put(2, "算法");
7.putAll:批量设置某些key对应的值
- putAll(Map map)
TreeMap<Integer, String> tree = new TreeMap<Integer, String>();
tree.put(2, "算法");
tree.put(3, "JAVA");
Map<Integer, String> testMap = new HashMap<Integer, String>(2);
testMap.put(1, "数据结构");
testMap.put(100, "Docker虚拟化容器");
tree.putAll(testMap); //{1=数据结构, 2=算法, 3=JAVA, 100=Docker虚拟化容器}
8.remove:删除某个key对应的值
- remove(Object key):通过key来删除节点
- remove(Object key, Object value):通过键和值两个条件来删除节点(如果key或者value不满足,则删除失败)
TreeMap<Integer, String> tree = new TreeMap<Integer, String>();
tree.put(2, "算法");
tree.put(3, "JAVA");
tree.put(1, "数据结构");
tree.remove(2);
tree.remove(3, "PHP"); //{1=数据结构, 3=JAVA}
9.keySet:获取树的所有节点的key集合
TreeMap<Integer, String> tree = new TreeMap<Integer, String>();
tree.put(2, "算法");
tree.put(3, "JAVA");
tree.put(1, "数据结构");
Set<Integer> keys = tree.keySet();
System.out.println(keys); // [1, 2, 3]
10.entrySet:获取所有节点集合(包括键和值)
TreeMap<Integer, String> tree = new TreeMap<Integer, String>();
tree.put(9, "算法");
tree.put(3, "JAVA");
tree.put(1, "数据结构");
System.out.println(tree.entrySet()); //[1=数据结构, 3=JAVA, 9=算法]
11.values:获取所有节点的值的集合
TreeMap<Integer, String> tree = new TreeMap<Integer, String>();
tree.put(9, "算法");
tree.put(3, "JAVA");
tree.put(1, "数据结构");
System.out.println(tree.values()); //[数据结构, JAVA, 算法]
12.replace:将指定的key的值替换成新的值
它与put不同的是,当key不存在时,不会执行插入操作
- replace(K key):替换指定key的值
- replace(K key, V oldValue, V newValue):将指定key和oldValue的替换成新的值
TreeMap<Integer, String> tree = new TreeMap<Integer, String>();
tree.put(9, "算法");
tree.put(3, "JAVA");
tree.put(5, "数据结构");
tree.replace(3, "Golang"); //{3=Golang, 5=数据结构, 9=算法}
tree.replace(1, "Python"); //{3=Golang, 5=数据结构, 9=算法}
tree.replace(5, "数据结构", "Java数据结构"); //{3=Golang, 5=Java数据结构, 9=算法}
tree.replace(5, "结构", "库"); //{3=Golang, 5=Java数据结构, 9=算法}
13.firstKey:获取树节点集合中的第一个键(因为它是有序的,所以也是最小的节点的键)
TreeMap<Integer, String> tree = new TreeMap<Integer, String>();
tree.put(9, "算法");
tree.put(3, "JAVA");
tree.put(5, "数据结构");
System.out.println(tree.firstKey()); //3
14.lowerKey:查找与指定键更少的一个键
- lowerKey(K key):在所有节点中查找比key更小而且最接近的一个key
TreeMap<Integer, String> tree = new TreeMap<Integer, String>();
tree.put(9, "算法");
tree.put(3, "JAVA");
tree.put(5, "数据结构");
System.out.println(tree.lowerKey(5)); //3
System.out.println(tree.lowerKey(8)); //5
三、TreeMap的实现原理解读
1.TreeMap的组成
TreeMap本质上是一颗二叉树,它由0至多个树节点组成,而它与普通的二叉树的区别就是,它的节点不仅有数据,指向左子树的指针和右子树的指针,还有一个节点的颜色,并且满足红黑的一些特点
(1).TreeMap对象的组成部分
- comparator : 元素比较器对象
- size : 节点的数目
- root : 根节点
(2).TreeMap节点(TreeMap.Entry对象)的特点
- key : 节点的键
- value : 节点的值
- left : 节点的左子树节点
- right : 节点的右子树节点
- parent : 节点的父节点
- color : 节点的颜色
2.TreeMap的实例化
//通过一个指定的map来初始化,将map中的元素添加到当前树中
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
//通过putAll来实现(putAll方法后面会讲解)
putAll(m);
}
3.TreeMap节点的插入和更新put
- 如果是空树,即根结果为null
- 插入的节点就为根节点
- 如果是非空树
- (1)对应的key是否存在,存在则更新值
- (2)不存在,则插入值,并旋转,以满足红黑树的条件
public V put(K key, V value) {
//定义一个指针节点t,初始指向根节点
Entry<K,V> t = root;
//如果是空树,即根节点为null
if (t == null) {
compare(key, key); // type (and possibly null) check
//插入的节点就是新的根节点
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
//定义一个比较方向
int cmp;
//父节点指针
Entry<K,V> parent;
// split comparator and comparable paths
//比较器
Comparator<? super K> cpr = comparator;
//如果有指定的比较器
if (cpr != null) {
do {
parent = t;
//当前插入的key与父节点的key大小作对比
cmp = cpr.compare(key, t.key);
//如果比父节点的key小,则插入的节点往左子树走
if (cmp < 0)
t = t.left;
//如果比父节点的key大,则插入的节点往右子树走
else if (cmp > 0)
t = t.right;
//如果相等,即存在相同key的旧节点,更新其值
else
return t.setValue(value);
} while (t != null);
}
else {
//如果没有指定的比较器,则使用具体对象的比较方法
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
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);
}
//如果插入的值是新节点,那么先生成一个新节点
Entry<K,V> e = new Entry<>(key, value, parent);
//如果插入节点key比父节点小,则插入到父节点的左子树上
if (cmp < 0)
parent.left = e;
else
//如果插入节点key比父节点大,则插入到父节点的右子树上
parent.right = e;
//红黑树着色和旋转,使之成为合法的红黑树
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
4.TreeMap节点的删除remove
public V remove(Object key) {
//1.通过key查找到相应的节点
Entry<K,V> p = getEntry(key);
//2.如果节点不存在,则直接返回
if (p == null)
return null;
//3.获取删除节点的旧值
V oldValue = p.value;
//4.删除节点(通过找到前序后继节点来替换和红黑树重新着色与扭转)
deleteEntry(p);
return oldValue;
}
5.TreeMap节点的查找get
- 通过二叉树查找方法进行查找
- 从根节点开始查找比较,如果比当前节点小,则进入左子树查找比较
- 如果比当前节点大,则进入右子树进行查找比较,直到找到对应的key为止
public V get(Object key) {
/*------------------------------------------------------------
final Entry<K,V> getEntry(Object key) {
//如果有指定的比较器,则使用比较器来查找元素节点
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
//使用key对应的具体的数据类型的比较方法来比较
Comparable<? super K> k = (Comparable<? super K>) key;
//从根节点开始查找
Entry<K,V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
-------------------------------------------------------------*/
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
6.ContainsKey的实现解读
原理:通过key在二叉树上遍历查找到key对应的节点,如果能找到,则说明key存在,否则key不存在
public boolean containsKey(Object key) {
/*--------------------------------------------------------------------
//通过比较器来查找节点
final Entry<K,V> getEntryUsingComparator(Object key) {
@SuppressWarnings("unchecked")
K k = (K) key;
Comparator<? super K> cpr = comparator;
if (cpr != null) {
//定义一个查找指针,从根节点开始查找
Entry<K,V> p = root;
while (p != null) {
//指针遍历的对象的key与当前要查找的key进行比较
int cmp = cpr.compare(k, p.key);
//如果比当前指针小,则在左子树
if (cmp < 0)
p = p.left;
//如果比当前指针大,则在右子树
else if (cmp > 0)
p = p.right;
//如果相同,直接返回对象
else
return p;
}
}
return null;
}
//根据某个key查找key对应的节点对象
final Entry<K,V> getEntry(Object key) {
// 如果在实例化的时候指定了比较器,直接使用
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
//定义一个用于遍历的指针,开始从根节点开始查找
Entry<K,V> p = root;
while (p != null) {
//比较当前指针与要查找的节点的key进行对比(根据其实际的类型调用各自的compareTo方法)
int cmp = k.compareTo(p.key);
//如果比当前指针小,则在当前节点的左子树上
if (cmp < 0)
p = p.left;
//如果比当前的指针大,则在当前节点的右子树上
else if (cmp > 0)
p = p.right;
//如果相等,也直接返回
else
return p;
}
return null;
}
---------------------------------------------------------------------*/
return getEntry(key) != null;
}