一、TreeMap 类概述
1、TreeMap存储 Key-Value 对时,要求key必须是由同一个类创建的对象,需要根据 key-value 对进行排序,TreeMap 可以保证所有的 Key-Value 对处于有序状态。
2、TreeSet底层使用红黑树结构存储数据
3、TreeMap 的 Key 的排序:
4、TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0。
5、TreeMap和HashMap一样实现的是Map接口,但两者的实现方式天差地别。HashMap的底层是hash表+单向链表的形式存储数据,TreeMap底层是通过红黑树存储数据。HashMap因为是基于散列表的实现,所以时间开销为O(1),TreeMap的时间开销是O(logn)。TreeMap的优势在于他是基于key值排序的。
关于红黑树:
红黑树有五大特性:
1)每个结点要么是红的,要么是黑的。
2)根结点是黑的。
3)每个叶结点,即空结点(NIL)是黑的。
4)如果一个结点是红的,那么它的俩个儿子都是黑的。
5)对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。
他的所有操作都是围绕着这五大特性展开的。这五大特性的最终目的就是为了维持二叉树的相对平衡性。
当每次二叉树操作以后,有可能会出现违反特性的情况(也就是出现了失衡状况),这时二叉树需要通过左旋,右旋,重新着色等系列操作,再次找到平衡点。
红黑色的操作就两个要点。第一:遵循二叉查找树的规范,对所有元素进行排序,但这里存在着不确定情况,有可能出现左右子树深度极其不对称的情况,导致最坏时间复杂度出现O(n)的情况。 第二:正因为存在二叉树严重不平衡的情况,所以就出现了红黑二叉树,通过标记每个节点的颜色,动态的调整二叉树的结构,使其始终维持在相对平衡的状态,这样做的好处就是查找性能始终维持在O(logn)的较高水平。
二、TreeMap 类结构
1、TreeMap 类继承结构
2、TreeMap 类签名
1 2 3 | public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable{} |
TreeMap 实现了 Map 接口,其内部数据格式是“键-值对”的形式(Entry),排列顺序是按照键的顺序进行的。
3、TreeMap 方法列表
三、TreeMap 成员变量
1 /**
2 * The comparator used to maintain order in this tree map, or
3 * null if it uses the natural ordering of its keys.
4 *
5 * TreeMap 内部的比较器,若为空,则为自然顺序
6 */
7 private final Comparator<? super K> comparator;
8
9 // 根节点
10 private transient Entry<K,V> root;
11
12 /**
13 * The number of entries in the tree
14 */
15 private transient int size = 0;
16
17 /**
18 * The number of structural modifications to the tree.
19 */
20 private transient int modCount = 0;
四、TreeMap 构造器
TreeMap 有四个构造器,分别如下:
构造器一:无参构造器
1 /**
2 * 无参构造器。使用 key 的自然顺序排列(key 要实现 Comparable 接口)
3 */
4 public TreeMap() {
5 comparator = null;
6 }
构造器二:
1 /**
2 * 使用指定的 Comparator(比较器)构造一个空的 TreeMap
3 */
4 public TreeMap(Comparator<? super K> comparator) {
5 this.comparator = comparator;
6 }
构造器三:
1 /**
2 * 使用给定的 Map 构造一个 TreeMap
3 */
4 public TreeMap(Map<? extends K, ? extends V> m) {
5 comparator = null;
6 putAll(m);
7 }
构造器四:
1 /**
2 * 使用给定的 SortedMap 构造一个 TreeMap
3 *(使用 SortedMap 的顺序)
4 */
5 public TreeMap(SortedMap<K, ? extends V> m) {
6 comparator = m.comparator();
7 try {
8 buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
9 } catch (java.io.IOException cannotHappen) {
10 } catch (ClassNotFoundException cannotHappen) {
11 }
12 }
五、常用方法
1、查找某个 key
1 // 判断 TreeMap 是否包含某个 key
2 public boolean containsKey(Object key) {
3 return getEntry(key) != null;
4 }
5
6 // 查找 TreeMap 中某个 key 对应的 value(若不存在返回 null)
7 public V get(Object key) {
8 Entry<K,V> p = getEntry(key);
9 return (p==null ? null : p.value);
10 }
由于这两个方法内部都是通过 getEntry 方法实现,因此放在一起分析,如下:
1 final Entry<K,V> getEntry(Object key) {
2 // Offload comparator-based version for sake of performance
3 if (comparator != null)
4 return getEntryUsingComparator(key);
5 if (key == null)
6 throw new NullPointerException();
7 @SuppressWarnings("unchecked")
8 Comparable<? super K> k = (Comparable<? super K>) key;
9 Entry<K,V> p = root;
10 while (p != null) {
11 int cmp = k.compareTo(p.key);
12 if (cmp < 0)
13 p = p.left;
14 else if (cmp > 0)
15 p = p.right;
16 else
17 return p;
18 }
19 return null;
20 }
当 Comparator 不为空时,使用如下方法查找:
1 /**
2 * Version of getEntry using comparator. Split off from getEntry
3 * for performance. (This is not worth doing for most methods,
4 * that are less dependent on comparator performance, but is
5 * worthwhile here.)
6 */
7 final Entry<K,V> getEntryUsingComparator(Object key) {
8 @SuppressWarnings("unchecked")
9 K k = (K) key;
10 Comparator<? super K> cpr = comparator;
11 if (cpr != null) {
12 Entry<K,V> p = root;
13 while (p != null) {
14 int cmp = cpr.compare(k, p.key);
15 if (cmp < 0)
16 p = p.left;
17 else if (cmp > 0)
18 p = p.right;
19 else
20 return p;
21 }
22 }
23 return null;
24 }
可以看到,这两个方法都是二叉查找树的查找过程。
PS: 这里将 Comporator 和 Comparable 两个接口分开进行操作。注释说明是出于性能考虑,虽然大部分方法中不值得这样做,但这里值得。
2、查找某个 value
1 public boolean containsValue(Object value) {
2 for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
3 if (valEquals(value, e.value))
4 return true;
5 return false;
6 }
getFirstEntry() 方法是获取第一个 Entry 节点(中序遍历最左边的节点):
1 /**
2 * Returns the first Entry in the TreeMap (according to the TreeMap's
3 * key-sort function). Returns null if the TreeMap is empty.
4 */
5 final Entry<K,V> getFirstEntry() {
6 Entry<K,V> p = root;
7 if (p != null)
8 while (p.left != null)
9 p = p.left;
10 return p;
11 }
查找某个 Entry 的后继节点:
1 /**
2 * Returns the successor of the specified Entry, or null if no such.
3 */
4 static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
5 if (t == null)
6 return null;
7 // 若右子树不为空,则后继节点就是右子树的最小节点
8 else if (t.right != null) {
9 Entry<K,V> p = t.right;
10 while (p.left != null)
11 p = p.left;
12 return p;
13 } else {
14 // 若右子树为空,则向上回溯
15 Entry<K,V> p = t.parent;
16 Entry<K,V> ch = t;
17 while (p != null && ch == p.right) {
18 ch = p;
19 p = p.parent;
20 }
21 return p;
22 }
23 }
可以看到,这里判断 TreeMap 是否包含某个 value,是按照二叉查找树的中序遍历去比较是否存在与给定 value 相等的值。
3、lowerEntry / lowerKey: 查找比指定 key 小的最大 Entry / key
1 public Map.Entry<K,V> lowerEntry(K key) {
2 return exportEntry(getLowerEntry(key));
3 }
4
5 public K lowerKey(K key) {
6 return keyOrNull(getLowerEntry(key));
7 }
1 /**
2 * Returns the entry for the greatest key less than the specified key; if
3 * no such entry exists (i.e., the least key in the Tree is greater than
4 * the specified key), returns {@code null}.
5 */
6 final Entry<K,V> getLowerEntry(K key) {
7 Entry<K,V> p = root;
8 while (p != null) {
9 int cmp = compare(key, p.key);
10 // 给定的key大于根节点,继续与右子节点比较
11 if (cmp > 0) {
12 if (p.right != null)
13 p = p.right;
14 else
15 return p;
16 } else {
17 // 左子节点不为空,则为左子节点
18 if (p.left != null) {
19 p = p.left;
20 } else {
21 // 左子节点为空,向父节点上溯
22 Entry<K,V> parent = p.parent;
23 Entry<K,V> ch = p;
24 while (parent != null && ch == parent.left) {
25 ch = parent;
26 parent = parent.parent;
27 }
28 return parent;
29 }
30 }
31 }
32 return null;
33 }
4、higherEntry / higherKey: 查找比指定 key 大的最小 Entry / key
1 public Map.Entry<K,V> higherEntry(K key) {
2 return exportEntry(getHigherEntry(key));
3 }
4
5 public K higherKey(K key) {
6 return keyOrNull(getHigherEntry(key));
7 }
getHigherEntry 方法与 getLowerEntry 方法实现类似,不同之处在于 left 和 right 相反,这里不再贴代码。
5、floorEntry / floorKey:
1 public Map.Entry<K,V> floorEntry(K key) {
2 return exportEntry(getFloorEntry(key));
3 }
4
5 public K floorKey(K key) {
6 return keyOrNull(getFloorEntry(key));
7 }
1 /**
2 * Gets the entry corresponding to the specified key; if no such entry
3 * exists, returns the entry for the greatest key less than the specified
4 * key; if no such entry exists, returns {@code null}.
5 */
6 final Entry<K,V> getFloorEntry(K key) {
7 Entry<K,V> p = root;
8 while (p != null) {
9 int cmp = compare(key, p.key);
10 if (cmp > 0) {
11 if (p.right != null)
12 p = p.right;
13 else
14 return p;
15 } else if (cmp < 0) {
16 if (p.left != null) {
17 p = p.left;
18 } else {
19 Entry<K,V> parent = p.parent;
20 Entry<K,V> ch = p;
21 while (parent != null && ch == parent.left) {
22 ch = parent;
23 parent = parent.parent;
24 }
25 return parent;
26 }
27 } else
28 // 与上述方法的区别
29 return p;
30 }
31 return null;
32 }
查找指定 key 关联的 Entry;若不存在,返回比该 key 小的最大 key 关联的 Entry;若这也不存在则返回 null。
PS: 该方法与上面的 getLowerEntry 方法仅相差 while 循环内部的一个 else。
6、ceilingEntry / ceilKey:
1 public Map.Entry<K,V> ceilingEntry(K key) {
2 return exportEntry(getCeilingEntry(key));
3 }
4
5 public K ceilingKey(K key) {
6 return keyOrNull(getCeilingEntry(key));
7 }
getCeilingEntry 方法与 getFloorEntry 方法实现类似,也是 left 和 right 相反。就像上面 getLowerEntry 和 getHigherEntry 的区别那样,这里不再贴代码。
查找指定 key 关联的 Entry;若不存在,返回比该 key 大的最小 key 关联的 Entry;若这也不存在则返回 null。
7、其他方法
1 public NavigableMap<K,V> headMap(K toKey, boolean inclusive) {
2 return new AscendingSubMap<>(this,
3 true, null, true,
4 false, toKey, inclusive);
5 }
1 public NavigableMap<K,V> tailMap(K fromKey, boolean inclusive) {
2 return new AscendingSubMap<>(this,
3 false, fromKey, inclusive,
4 true, null, true);
5 }
1 public NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,
2 K toKey, boolean toInclusive) {
3 return new AscendingSubMap<>(this,
4 false, fromKey, fromInclusive,
5 false, toKey, toInclusive);
6 }
六、插入操作
该操作其实就是红黑树的插入节点操作。前面分析过,红黑树是一种平衡二叉树,新增节点后可能导致其失去平衡,因此需要对其进行修复操作以维持其平衡性。插入操作的代码如下:
1 public V put(K key, V value) {
2 Entry<K,V> t = root;
3 // 若 root 节点为空,则直接插入(为根节点)
4 if (t == null) {
5 compare(key, key); // type (and possibly null) check
6 root = new Entry<>(key, value, null);
7 size = 1;
8 modCount++;
9 return null;
10 }
11 int cmp;
12 Entry<K,V> parent;
13 // split comparator and comparable paths
14 // 拆分 Comparator 接口和 Comparable 接口(上文 getEntry 方法也是如此)
15 Comparator<? super K> cpr = comparator;
16 if (cpr != null) {
17 do {
18 parent = t;
19 cmp = cpr.compare(key, t.key);
20 if (cmp < 0)
21 t = t.left;
22 else if (cmp > 0)
23 t = t.right;
24 else
25 // 若key已存在,则替换其对应的value
26 return t.setValue(value);
27 } while (t != null);
28 }
29 else {
30 if (key == null)
31 throw new NullPointerException();
32 @SuppressWarnings("unchecked")
33 Comparable<? super K> k = (Comparable<? super K>) key;
34 do {
35 parent = t;
36 cmp = k.compareTo(t.key);
37 if (cmp < 0)
38 t = t.left;
39 else if (cmp > 0)
40 t = t.right;
41 else
42 return t.setValue(value);
43 } while (t != null);
44 }
45 Entry<K,V> e = new Entry<>(key, value, parent);
46 if (cmp < 0)
47 parent.left = e;
48 else
49 parent.right = e;
50 // 插入节点后的平衡性调整
51 fixAfterInsertion(e);
52 size++;
53 modCount++;
54 return null;
55 }
对应的几种插入节点修复操作前文「数据结构与算法笔记」已进行了分析,为了便于分析和理解代码,这里把图再贴一下(下图为关注节点的父节点是其祖父节点的左子节点的情况,在右边时操作类似):
case1: 关注节点 a 的叔叔节点为红色
case2: 关注节点为 a,它的叔叔节点 d 是黑色,a 是其父节点 b 的右子节点
case3: 关注节点是 a,它的叔叔节点 d 是黑色,a 是其父节点 b 的左子节点
插入操作的平衡调整代码如下:
1 private void fixAfterInsertion(Entry<K,V> x) {
2 // 新插入的节点为红色
3 x.color = RED;
4 // 只有在父节点为红色时需要进行插入修复操作
5 while (x != null && x != root && x.parent.color == RED) {
6 // 下面两种情况是左右对称的
7 // x 的父节点是它祖父节点的左子节点
8 if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
9 // 叔叔节点
10 Entry<K,V> y = rightOf(parentOf(parentOf(x)));
11 // case1
12 if (colorOf(y) == RED) {
13 setColor(parentOf(x), BLACK);
14 setColor(y, BLACK);
15 setColor(parentOf(parentOf(x)), RED);
16 x = parentOf(parentOf(x));
17 } else {
18 // case2
19 if (x == rightOf(parentOf(x))) {
20 x = parentOf(x);
21 rotateLeft(x);
22 }
23 // case3
24 setColor(parentOf(x), BLACK);
25 setColor(parentOf(parentOf(x)), RED);
26 rotateRight(parentOf(parentOf(x)));
27 }
28 }
29 // x 的父节点是它祖父节点的右子节点(与上面情况对称)
30 else {
31 Entry<K,V> y = leftOf(parentOf(parentOf(x)));
32 if (colorOf(y) == RED) {
33 setColor(parentOf(x), BLACK);
34 setColor(y, BLACK);
35 setColor(parentOf(parentOf(x)), RED);
36 x = parentOf(parentOf(x));
37 } else {
38 if (x == leftOf(parentOf(x))) {
39 x = parentOf(x);
40 rotateRight(x);
41 }
42 setColor(parentOf(x), BLACK);
43 setColor(parentOf(parentOf(x)), RED);
44 rotateLeft(parentOf(parentOf(x)));
45 }
46 }
47 }
48 root.color = BLACK;
49 }
七、删除操作
remove() 方法:
1 public V remove(Object key) {
2 Entry<K,V> p = getEntry(key);
3 if (p == null)
4 return null;
5 V oldValue = p.value;
6 deleteEntry(p);
7 return oldValue;
8 }
内部实现方法如下:
1 /**
2 * Delete node p, and then rebalance the tree.
3 */
4 private void deleteEntry(Entry<K,V> p) {
5 modCount++;
6 size--;
7 // If strictly internal, copy successor's element to p and then make p
8 // point to successor.
9 // 左右子树都不为空,寻找后继节点
10 if (p.left != null && p.right != null) {
11 Entry<K,V> s = successor(p);
12 p.key = s.key;
13 p.value = s.value;
14 p = s;
15 } // p has 2 children
16 // Start fixup at replacement node, if it exists.
17 Entry<K,V> replacement = (p.left != null ? p.left : p.right);
18 if (replacement != null) {
19 // Link replacement to parent
20 replacement.parent = p.parent;
21 if (p.parent == null)
22 root = replacement;
23 else if (p == p.parent.left)
24 p.parent.left = replacement;
25 else
26 p.parent.right = replacement;
27 // Null out links so they are OK to use by fixAfterDeletion.
28 p.left = p.right = p.parent = null;
29 // Fix replacement
30 if (p.color == BLACK)
31 fixAfterDeletion(replacement);
32 } else if (p.parent == null) { // return if we are the only node.
33 // 只有一个根节点
34 root = null;
35 } else { // No children. Use self as phantom replacement and unlink.
36 if (p.color == BLACK)
37 fixAfterDeletion(p);
38 if (p.parent != null) {
39 if (p == p.parent.left)
40 p.parent.left = null;
41 else if (p == p.parent.right)
42 p.parent.right = null;
43 p.parent = null;
44 }
45 }
46 }
几种删除操作情况如下(下图为关注节点为父节点的左子节点的情况,关注节点为父节点的右子节点情况时的操作对称):
case1: 关注节点的兄弟节点是红色
case2: 关注节点的兄弟节点是黑色,且兄弟节点的子节点都是黑色
case3: 关注节点的兄弟节点是黑色,且左子节点是红色、右子节点是黑色
case4: 关注节点的兄弟节点是黑色,且右子节点是红色、左子节点是黑色
两天又参考了其他文章及代码,这里的 case4 是目前经分析认为比较准确的(符合 JDK 1.8 源码中 TreeMap 的实现思路)。
删除操作的平衡调整代码如下:
1 private void fixAfterDeletion(Entry<K,V> x) {
2 // x 不为根节点,且颜色为黑色
3 while (x != root && colorOf(x) == BLACK) {
4 // x 是父节点的左子节点
5 if (x == leftOf(parentOf(x))) {
6 // 兄弟节点
7 Entry<K,V> sib = rightOf(parentOf(x));
8 // case1 待删除节点的兄弟节点为红色
9 if (colorOf(sib) == RED) {
10 setColor(sib, BLACK);
11 setColor(parentOf(x), RED);
12 rotateLeft(parentOf(x));
13 sib = rightOf(parentOf(x));
14 }
15 // case2 待删除节点的兄弟节点的子节点都为黑色
16 if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) {
17 setColor(sib, RED);
18 x = parentOf(x);
19 } else {
20 // case3 待删除节点的兄弟节点的左子节点为红色、右子节为黑色
21 if (colorOf(rightOf(sib)) == BLACK) {
22 setColor(leftOf(sib), BLACK);
23 setColor(sib, RED);
24 rotateRight(sib);
25 sib = rightOf(parentOf(x));
26 }
27 // case4 待删除节点的兄弟节点的左子节点为黑色、右子节为红色
28 setColor(sib, colorOf(parentOf(x)));
29 setColor(parentOf(x), BLACK);
30 setColor(rightOf(sib), BLACK); //??
31 rotateLeft(parentOf(x));
32 x = root;
33 }
34 }
35 // x 是父节点的右子节点(对称操作)
36 else { // symmetric
37 Entry<K,V> sib = leftOf(parentOf(x));
38 if (colorOf(sib) == RED) {
39 setColor(sib, BLACK);
40 setColor(parentOf(x), RED);
41 rotateRight(parentOf(x));
42 sib = leftOf(parentOf(x));
43 }
44 if (colorOf(rightOf(sib)) == BLACK &&
45 colorOf(leftOf(sib)) == BLACK) {
46 setColor(sib, RED);
47 x = parentOf(x);
48 } else {
49 if (colorOf(leftOf(sib)) == BLACK) {
50 setColor(rightOf(sib), BLACK);
51 setColor(sib, RED);
52 rotateLeft(sib);
53 sib = leftOf(parentOf(x));
54 }
55 setColor(sib, colorOf(parentOf(x)));
56 setColor(parentOf(x), BLACK);
57 setColor(leftOf(sib), BLACK);
58 rotateRight(parentOf(x));
59 x = root;
60 }
61 }
62 }
63 setColor(x, BLACK);
64 }
插入和删除操作相对复杂,容易被绕晕,但其实也是有规律可循的。对比操作的图解,可以更容易分析和理解。
八、TreeMap 案例
1、自然排序
集合中要放入的对象:需要重写 equals 和 hashCode 方法
1 public class User implements Comparable{ 2 private String name; 3 private int age; 4 5 public User() { 6 } 7 8 public User(String name, int age) { 9 this.name = name; 10 this.age = age; 11 } 12 13 public String getName() { 14 return name; 15 } 16 17 public void setName(String name) { 18 this.name = name; 19 } 20 21 public int getAge() { 22 return age; 23 } 24 25 public void setAge(int age) { 26 this.age = age; 27 } 28 29 @Override 30 public String toString() { 31 return "User{" + 32 "name='" + name + '\'' + 33 ", age=" + age + 34 '}'; 35 } 36 37 @Override 38 public boolean equals(Object o) { 39 System.out.println("User equals()...."); 40 if (this == o) return true; 41 if (o == null || getClass() != o.getClass()) return false; 42 43 User user = (User) o; 44 45 if (age != user.age) return false; 46 return name != null ? name.equals(user.name) : user.name == null; 47 } 48 49 @Override 50 public int hashCode() { //return name.hashCode() + age; 51 int result = name != null ? name.hashCode() : 0; 52 result = 31 * result + age; 53 return result; 54 } 55 56 //按照姓名从大到小排列,年龄从小到大排列 57 @Override 58 public int compareTo(Object o) { 59 if(o instanceof User){ 60 User user = (User)o; 61 // return -this.name.compareTo(user.name); 62 int compare = -this.name.compareTo(user.name); 63 if(compare != 0){ 64 return compare; 65 }else{ 66 return Integer.compare(this.age,user.age); 67 } 68 }else{ 69 throw new RuntimeException("输入的类型不匹配"); 70 } 71 72 } 73 }
1 @Test
2 public void test1(){
3 TreeMap map = new TreeMap();
4 User u1 = new User("Tom",23);
5 User u2 = new User("Jerry",32);
6 User u3 = new User("Jack",20);
7 User u4 = new User("Rose",18);
8
9 map.put(u1,98);
10 map.put(u2,89);
11 map.put(u3,76);
12 map.put(u4,100);
13
14 Set entrySet = map.entrySet();
15 Iterator iterator1 = entrySet.iterator();
16 while (iterator1.hasNext()){
17 Object obj = iterator1.next();
18 Map.Entry entry = (Map.Entry) obj;
19 System.out.println(entry.getKey() + "---->" + entry.getValue());
20
21 }
22 }
2、定制排序
1 //定制排序
2 @Test
3 public void test2(){
4 TreeMap map = new TreeMap(new Comparator() {
5 @Override
6 public int compare(Object o1, Object o2) {
7 if(o1 instanceof User && o2 instanceof User){
8 User u1 = (User)o1;
9 User u2 = (User)o2;
10 return Integer.compare(u1.getAge(),u2.getAge());
11 }
12 throw new RuntimeException("输入的类型不匹配!");
13 }
14 });
15 User u1 = new User("Tom",23);
16 User u2 = new User("Jerry",32);
17 User u3 = new User("Jack",20);
18 User u4 = new User("Rose",18);
19
20 map.put(u1,98);
21 map.put(u2,89);
22 map.put(u3,76);
23 map.put(u4,100);
24
25 Set entrySet = map.entrySet();
26 Iterator iterator1 = entrySet.iterator();
27 while (iterator1.hasNext()){
28 Object obj = iterator1.next();
29 Map.Entry entry = (Map.Entry) obj;
30 System.out.println(entry.getKey() + "---->" + entry.getValue());
31
32 }
33 }
九、
十、总结
1. TreeMap 实现了 Map 接口,内部节点类型为 Entry;
2. 基于红黑树实现,具有红黑树的特点;
3. 有序,根据 Entry 的 key 排序;
4. 查找、插入、删除操作的时间复杂度均为 O(logn)。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战